Skip to content

Commit 296a574

Browse files
emilkntjohnson1
andauthored
Py-SDK: More kw-args (#11384)
Co-authored-by: Nick <[email protected]>
1 parent efffc7e commit 296a574

34 files changed

+120
-54
lines changed

crates/build/re_types_builder/src/codegen/python/mod.rs

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
33
mod views;
44

5-
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
5+
use std::{
6+
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
7+
iter,
8+
};
69

710
use anyhow::Context as _;
811
use camino::{Utf8Path, Utf8PathBuf};
9-
use itertools::Itertools as _;
12+
use itertools::{Itertools as _, chain};
1013
use unindent::unindent;
1114

1215
use crate::{
@@ -267,7 +270,7 @@ impl ExtensionClass {
267270
.collect();
268271

269272
let valid_converter_overrides = if obj.is_union() {
270-
itertools::Either::Left(std::iter::once("inner"))
273+
itertools::Either::Left(iter::once("inner"))
271274
} else {
272275
itertools::Either::Right(obj.fields.iter().map(|field| field.name.as_str()))
273276
}
@@ -2434,10 +2437,13 @@ fn compute_init_parameters(obj: &Object, objects: &Objects) -> Vec<String> {
24342437
// If the type is fully transparent (single non-nullable field and not an archetype),
24352438
// we have to use the "{obj.name}Like" type directly since the type of the field itself might be too narrow.
24362439
// -> Whatever type aliases there are for this type, we need to pick them up.
2437-
if obj.kind != ObjectKind::Archetype && obj.fields.len() == 1 && !obj.fields[0].is_nullable {
2440+
if obj.kind != ObjectKind::Archetype
2441+
&& let [single_field] = obj.fields.as_slice()
2442+
&& !single_field.is_nullable
2443+
{
24382444
vec![format!(
24392445
"{}: {}",
2440-
obj.fields[0].name,
2446+
single_field.name,
24412447
quote_parameter_type_alias(&obj.fqname, &obj.fqname, objects, false)
24422448
)]
24432449
} else if obj.is_union() {
@@ -2460,17 +2466,19 @@ fn compute_init_parameters(obj: &Object, objects: &Objects) -> Vec<String> {
24602466
.map(|field| quote_init_parameter_from_field(field, objects, &obj.fqname))
24612467
.collect_vec();
24622468

2463-
if optional.is_empty() {
2464-
required
2465-
} else if obj.kind == ObjectKind::Archetype {
2466-
// Force kw-args for all optional arguments:
2469+
if 2 < required.len() {
2470+
// There's a lot of required arguments.
2471+
// Using positional arguments would make usage hard to read.
2472+
// better for force kw-args for ALL arguments:
2473+
chain!(std::iter::once("*".to_owned()), required, optional).collect()
2474+
} else if optional.is_empty() {
24672475
required
2468-
.into_iter()
2469-
.chain(std::iter::once("*".to_owned()))
2470-
.chain(optional)
2471-
.collect()
2476+
} else if obj.name == "AnnotationInfo" {
2477+
// TODO(#6836): rewrite AnnotationContext
2478+
chain!(required, optional).collect()
24722479
} else {
2473-
required.into_iter().chain(optional).collect()
2480+
// Force kw-args for all optional arguments:
2481+
chain!(required, std::iter::once("*".to_owned()), optional).collect()
24742482
}
24752483
}
24762484
}
@@ -2514,10 +2522,9 @@ fn quote_init_method(
25142522
ext_class: &ExtensionClass,
25152523
objects: &Objects,
25162524
) -> String {
2517-
let parameters = compute_init_parameters(obj, objects);
25182525
let head = format!(
25192526
"def __init__(self: Any, {}) -> None:",
2520-
parameters.join(", ")
2527+
compute_init_parameters(obj, objects).join(", ")
25212528
);
25222529

25232530
let parameter_docs = compute_init_parameter_docs(reporter, obj, objects);

docs/content/reference/migration/migration-0-26.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,26 @@ For non-blocking, use `timeout_sec=0`.
1010
Mostly you can just call `.flush()` with no arguments.
1111
That will block until all writes either finishes or an error occurs (e.g. the gRPC connection is severed).
1212

13+
## Python SDK: more use of kw-args
14+
We have started using named arguments (kw-args) for more of our functions.
15+
This will make it easier for us to evolve our APIs in the future, when adding new arguments, or renaming old ones.
16+
17+
Before:
18+
```py
19+
rr.ImageFormat(width, height, "YUV420")
20+
21+
blueprint.spawn("my_app", 1234)
22+
```
23+
24+
After:
25+
```py
26+
rr.ImageFormat(width, height, pixel_format="YUV420")
27+
28+
blueprint.spawn("my_app", port=1234)
29+
```
30+
31+
[ruff](https://github.com/astral-sh/ruff) (or your preferred Python linter) will find all the places in your code that need to be updated!
32+
1333
## Python DataFusion interface: update to 49.0.0
1434
The DataFusion FFI that we rely on for user defined functions and
1535
table providers requires users to upgrade their `datafusion-python`

docs/snippets/compare_snippet_output.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def main() -> None:
133133
build_cpp_snippets()
134134

135135
# Always build rust since we use it as the baseline for comparison.
136-
build_rust_snippets(build_env, args.release, args.target, args.target_dir)
136+
build_rust_snippets(build_env=build_env, release=args.release, target=args.target, target_dir=args.target_dir)
137137

138138
examples = []
139139
if len(args.example) > 0:
@@ -281,13 +281,17 @@ def run_example(example: Example, language: str, args: argparse.Namespace) -> No
281281
python_output_path = run_python(example)
282282
check_non_empty_rrd(python_output_path)
283283
elif language == "rust":
284-
rust_output_path = run_prebuilt_rust(example, args.release, args.target, args.target_dir)
284+
rust_output_path = run_prebuilt_rust(
285+
example, release=args.release, target=args.target, target_dir=args.target_dir
286+
)
285287
check_non_empty_rrd(rust_output_path)
286288
else:
287289
raise AssertionError(f"Unknown language: {language}")
288290

289291

290-
def build_rust_snippets(build_env: dict[str, str], release: bool, target: str | None, target_dir: str | None) -> None:
292+
def build_rust_snippets(
293+
*, build_env: dict[str, str], release: bool, target: str | None, target_dir: str | None
294+
) -> None:
291295
print("----------------------------------------------------------")
292296
print("Building snippets for Rust…")
293297

@@ -343,7 +347,7 @@ def run_python(example: Example) -> str:
343347
return output_path
344348

345349

346-
def run_prebuilt_rust(example: Example, release: bool, target: str | None, target_dir: str | None) -> str:
350+
def run_prebuilt_rust(example: Example, *, release: bool, target: str | None, target_dir: str | None) -> str:
347351
output_path = example.output_path("rust")
348352

349353
extension = ".exe" if os.name == "nt" else ""

pixi.lock

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rerun_py/pyproject.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ lint.select = [
155155
"I", # Isort
156156
"ISC", # Ensure implicit string concat syntax
157157
"PIE", # flake8-pic: various idomatic python lints
158+
"PLR0917", # too-many-positional-parameters
158159
"PLW1514", # Force setting `encoding` for open calls. This is in order to prevent issues when opening utf8 files on windows where the default encoding may depend on the active locale. https://docs.astral.sh/ruff/rules/unspecified-encoding/
159160
"RUF", # Ruff-specific rules
160161
"RUF027", # Ensure that strings which look like format-strings have the `f` prefix
@@ -176,6 +177,9 @@ lint.unfixable = [
176177
"PLW1514", # Automatic fix for `encoding` doesn't do what we want - it queries the locale for the preferred encoding which is exactly what we want to avoid.
177178
]
178179

180+
[tool.ruff.lint.pylint]
181+
max-positional-args = 3
182+
179183
[tool.ruff.lint.per-file-ignores]
180184
"*" = ["RUF012"] # TODO(emilk): consider enabling this
181185

@@ -193,6 +197,23 @@ lint.unfixable = [
193197
"RUF001",
194198
]
195199

200+
"examples/*" = [
201+
# We don't care about nice APIs in our examples:
202+
"PLR0917",
203+
]
204+
"scripts/*" = [
205+
# We don't care about nice APIs in our scripts:
206+
"PLR0917",
207+
]
208+
"rerun_py/tests/*" = [
209+
# We don't care about nice APIs in our tests:
210+
"PLR0917",
211+
]
212+
"tests/python/*" = [
213+
# We don't care about nice APIs in our tests:
214+
"PLR0917",
215+
]
216+
196217
[tool.ruff.lint.isort]
197218
required-imports = ["from __future__ import annotations"]
198219
combine-as-imports = true # needed so keep rerun_sdk/__init__.py clean

rerun_py/rerun_sdk/rerun/_send_columns.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ def send_columns(
247247
entity_path: str,
248248
indexes: Iterable[TimeColumnLike],
249249
columns: Iterable[ComponentColumn],
250+
*,
250251
recording: RecordingStream | None = None,
251252
strict: bool | None = None, # noqa: ARG001 - `strict` handled by `@catch_and_log_exceptions`
252253
) -> None:

rerun_py/rerun_sdk/rerun/any_batch_value.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ class AnyBatchValue(ComponentBatchLike):
127127
See also [rerun.AnyValues][].
128128
"""
129129

130-
def __init__(self, descriptor: str | ComponentDescriptor, value: Any, drop_untyped_nones: bool = True) -> None:
130+
def __init__(self, descriptor: str | ComponentDescriptor, value: Any, *, drop_untyped_nones: bool = True) -> None:
131131
"""
132132
Construct a new AnyBatchValue.
133133
@@ -248,5 +248,5 @@ def column(
248248
previously logged with a type.
249249
250250
"""
251-
inst = cls(descriptor, value, drop_untyped_nones)
251+
inst = cls(descriptor, value, drop_untyped_nones=drop_untyped_nones)
252252
return ComponentColumn(descriptor, inst)

rerun_py/rerun_sdk/rerun/any_value.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,12 @@ def __init__(self, drop_untyped_nones: bool = True, **kwargs: Any) -> None:
8484
"`rr.AnyValues.with_field` is deprecated, use `rr.AnyValues.with_component_from_data` instead.",
8585
)
8686
def with_field(
87-
self, descriptor: str | ComponentDescriptor, value: Any, drop_untyped_nones: bool = True
87+
self, descriptor: str | ComponentDescriptor, value: Any, *, drop_untyped_nones: bool = True
8888
) -> AnyValues:
89-
return self.with_component_from_data(descriptor, value, drop_untyped_nones)
89+
return self.with_component_from_data(descriptor, value, drop_untyped_nones=drop_untyped_nones)
9090

9191
def with_component_from_data(
92-
self, descriptor: str | ComponentDescriptor, value: Any, drop_untyped_nones: bool = True
92+
self, descriptor: str | ComponentDescriptor, value: Any, *, drop_untyped_nones: bool = True
9393
) -> AnyValues:
9494
"""Adds an `AnyValueBatch` to this `AnyValues` bundle."""
9595
# TODO(#10908): Prune this type in 0.26
@@ -108,13 +108,15 @@ def with_component_from_data(
108108
@deprecated(
109109
"`rr.AnyValues.with_component` is deprecated, use `rr.AnyValues.with_component_override` instead.",
110110
)
111-
def with_component(self, field: str, component_type: str, value: Any, drop_untyped_nones: bool = True) -> AnyValues:
111+
def with_component(
112+
self, field: str, component_type: str, value: Any, *, drop_untyped_nones: bool = True
113+
) -> AnyValues:
112114
"""Adds an `AnyValueBatch` to this `AnyValues` bundle with name and component type."""
113115
self._builder.with_component_override(field, component_type, value, drop_untyped_nones=drop_untyped_nones)
114116
return self
115117

116118
def with_component_override(
117-
self, field: str, component_type: str, value: Any, drop_untyped_nones: bool = True
119+
self, field: str, component_type: str, value: Any, *, drop_untyped_nones: bool = True
118120
) -> AnyValues:
119121
"""Adds an `AnyValueBatch` to this `AnyValues` bundle with name and component type."""
120122
self._builder.with_component_override(field, component_type, value, drop_untyped_nones=drop_untyped_nones)

rerun_py/rerun_sdk/rerun/archetypes/mcap_channel.py

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rerun_py/rerun_sdk/rerun/archetypes/mcap_schema.py

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)