Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR renames the Python runtime library from Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Merging this PR will degrade performance by 27.92%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | WallTime | bench_incremental_add_string_char |
25.8 ms | 30.6 ms | -15.93% |
| ❌ | WallTime | bench_incremental_modify_function |
11.2 ms | 13.6 ms | -17.94% |
| ❌ | WallTime | bench_scale_deep_nesting |
29.5 ms | 33.1 ms | -10.85% |
| ❌ | WallTime | bench_incremental_rename_type |
40.1 ms | 46.5 ms | -13.78% |
| ❌ | WallTime | bench_scale_100_functions |
116.8 ms | 142.7 ms | -18.13% |
| ❌ | WallTime | bench_parse_only_simple |
30.7 µs | 40.8 µs | -24.58% |
| ⚡ | WallTime | bench_single_simple_file |
34.5 ms | 20.6 ms | +67.15% |
| ❌ | WallTime | bench_lexer_only_simple |
17.2 µs | 23.9 µs | -27.92% |
| ❌ | WallTime | bench_incremental_no_change |
8.9 ms | 10.9 ms | -18.3% |
| ❌ | WallTime | bench_incremental_add_user_field |
32.1 ms | 38.3 ms | -16.24% |
| ❌ | WallTime | bench_incremental_add_field |
11.3 ms | 13.5 ms | -16.41% |
| ❌ | WallTime | bench_incremental_close_string |
25.9 ms | 30.8 ms | -15.93% |
| ❌ | WallTime | bench_incremental_add_attribute |
27.3 ms | 30.9 ms | -11.54% |
| ❌ | WallTime | bench_incremental_add_new_file |
14.7 ms | 17.7 ms | -17.28% |
Comparing push-ymoymwlnvtsn (d9c15f2) with canary (53df3e2)
Footnotes
-
105 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9c40407b8b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let companions: Vec<Function> = function | ||
| .companions | ||
| .iter() | ||
| .map(|(_, companion)| { |
There was a problem hiding this comment.
Use companion suffix for generated Python companion methods
In compiler-produced object pools, companion function names are synthesized as strings like Parent$build_request; this mapping discards the tuple key (the intended companion suffix) and keeps the raw function name, so the renderer later emits identifiers containing $ for def names and attribute assignments. That produces invalid Python syntax and makes generated clients fail to import whenever companion functions are present. The Python-facing method name should come from the stored suffix, while the full internal name can stay in the wire/runtime call.
Useful? React with 👍 / 👎.
| baml_codegen_types::Namespace::Types { .. } => Self::Types, | ||
| baml_codegen_types::Namespace::StreamTypes { .. } => Self::StreamTypes, |
There was a problem hiding this comment.
Preserve namespace paths when mapping codegen names
This conversion collapses path-carrying namespaces into flat Types/StreamTypes variants, which drops the namespace path information needed to render cross-namespace references correctly. When a type in one namespace references a type alias/class in another namespace, the renderer can no longer qualify it with the correct module path and may emit unresolved local names, leading to broken annotations/import-time failures in namespaced clients.
Useful? React with 👍 / 👎.
| import atexit | ||
|
|
||
| from .baml_py import ( | ||
| from .baml import ( |
There was a problem hiding this comment.
Import baml_py wrappers from the new top-level baml package
After renaming the extension module to baml, this relative import points to baml_py.baml, which is not produced by the new build layout. That means import baml_py will fail before it can provide compatibility exports, breaking existing users still on the baml_py import path. The compatibility package should import from the new baml package (or ship an explicit shim module) instead of relative .baml.
Useful? React with 👍 / 👎.
Binary size checks passed✅ 7 passed
Generated by |
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
baml_language/crates/bridge_python/python_src/baml_py/__init__.py (1)
5-17:⚠️ Potential issue | 🔴 CriticalFix the import to use the absolute path instead of relative.
The import
from .baml import ...will fail at runtime becausebamlis a sibling package, not a submodule ofbaml_py. Change tofrom baml import ....🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/bridge_python/python_src/baml_py/__init__.py` around lines 5 - 17, The current relative import "from .baml import ..." will fail because baml is a sibling package; change the import in __init__.py to use an absolute import "from baml import AbortController, BamlRuntime, Collector as _RustCollector, FunctionLog as _RustFunctionLog, FunctionResult, HostSpanManager, LLMCall, Timing, Usage, flush_events, get_version" so the symbols AbortController, BamlRuntime, _RustCollector, _RustFunctionLog, FunctionResult, HostSpanManager, LLMCall, Timing, Usage, flush_events and get_version are imported from the sibling package correctly.baml_language/crates/baml_codegen_python/src/ty.rs (1)
16-20:⚠️ Potential issue | 🟠 MajorPreserve and compare full namespace paths when rendering cross-module type references.
The conversion from
baml_codegen_types::Namespacetobaml_codegen_python::Namespaceat lines 113–114 discards thepathfield using.., andName::render(line 55) compares only the coarse enum variant. When a class in one directory references a type in another (e.g.,baml_types/foo/referencing a class inbaml_types/bar/), both lose their path information and render as bare names instead of qualified imports, causingNameErrorat runtime.The code correctly partitions types by path into separate
NamespaceGroups, but the rendering logic doesn't use that information. ExtendNameandRenderCtxto carry the full path, updateName::renderto compare complete module paths for locality detection, and ensure cross-path references are either qualified or imported. Add unit tests covering types from different paths referencing each other and confirmcargo test --libpasses.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/ty.rs` around lines 16 - 20, The Name struct currently loses namespace path data and Name::render only compares the enum variant, causing cross-directory types to render as bare names; fix by preserving the full namespace path when converting baml_codegen_types::Namespace into baml_codegen_python::Namespace (remove the use of .. that drops path), extend Name to store the complete namespace.path and thread that path through RenderCtx, update Name::render to compare full module paths (not just the enum variant) to decide locality, and alter rendering so references from different paths are emitted as qualified imports or fully-qualified names (or added to imports) consistent with NamespaceGroup partitioning; update or add unit tests that create types in different paths referencing each other to assert correct qualified import/render behavior and ensure cargo test --lib passes.
🧹 Nitpick comments (3)
baml_language/crates/baml_codegen_python/src/objects/render/type_alias.rs (1)
152-179: Test is tied to a parser quirk — will break silently if suffix-first parsing changes.The expected output
typing.List[typing.Union[int, "RecursiveAlias"]]depends on the test builder parsing"int | RecursiveAlias[]"asList(Union(int, RecursiveAlias))rather thanUnion(int, List(RecursiveAlias)). The comment at lines 156-157 acknowledges this. Consider constructing theTyexplicitly (e.g.Ty::Union([int, List(self_ref)])) so the test asserts the self-ref quoting behavior rather than the parser precedence; otherwise a future precedence fix would silently invert what this test is actually verifying.Also: the
use baml_codegen_tests::ty;on line 158 appears unused since the test constructsCgTypeAliasviaty(...)at line 166 — the import is used, ignore that.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/objects/render/type_alias.rs` around lines 152 - 179, The test type_alias_recursive_self_ref relies on parser precedence via ty("int | RecursiveAlias[]"); instead, build the resolved type explicitly so the test asserts quoting behavior regardless of parser changes: replace the resolves_to: ty("int | RecursiveAlias[]") in the CgTypeAlias passed to TypeAlias::from_codegen_types with an explicit Ty constructed as a Union of int and List(self_ref) (e.g., Ty::Union([Ty::Int, Ty::List(Ty::Ref("RecursiveAlias"))]) using the test Ty constructors), and keep the rest of the test (including the assertion for quoted "RecursiveAlias") unchanged so it verifies self-ref quoting independent of parser quirks.baml_language/rig_tests/crates/python_packages_and_namespaces/customizable/test_main.py (1)
32-34: Test name overpromises.
test_stream_types_mirrors_structureonly asserts thatbaml_client.baml_stream_typesis importable; it does not actually verify that the stream-types tree mirrors the structure asserted in the preceding tests. Consider either renaming totest_stream_types_importableor adding mirrored-structure assertions (e.g.,baml_client.baml_stream_types.foo.Sentiment,baml_client.baml_stream_types.other.foo.Address).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/rig_tests/crates/python_packages_and_namespaces/customizable/test_main.py` around lines 32 - 34, The test named test_stream_types_mirrors_structure currently only checks importability of baml_client.baml_stream_types; update it to either be renamed to test_stream_types_importable to reflect its current behavior or extend it to assert the actual mirrored structure by verifying expected attributes and nested members on the imported module (e.g., check that baml_client.baml_stream_types has attributes like foo and that foo.Sentiment exists, and that nested paths such as baml_client.baml_stream_types.other.foo.Address are present) — modify the test_stream_types_mirrors_structure function to perform attribute checks (using hasattr or attribute access assertions) against those specific symbols to ensure the tree mirrors the expected structure.baml_language/crates/bridge_python/python_src/baml_py/ctx_manager.py (1)
192-192: Consider moving the import to the top-level for consistency.The
flush_eventsimport is performed locally within theflush()method, whileBamlRuntimeandHostSpanManagerare imported from the same.bamlmodule at the top of the file (line 22). Unless there's a specific reason for the local import (e.g., avoiding circular dependencies), consolidating all imports from.bamlat the module level would improve consistency.♻️ Proposed refactor to consolidate imports
-from .baml import BamlRuntime, HostSpanManager +from .baml import BamlRuntime, HostSpanManager, flush_eventsThen remove the local import:
def flush(self) -> None: """Flush all buffered trace events to the JSONL file. Delegates to the global publisher thread in bex_events which handles BAML_TRACE_FILE internally. """ - from .baml import flush_events - flush_events()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/bridge_python/python_src/baml_py/ctx_manager.py` at line 192, The local import of flush_events inside the flush() method should be moved to the module top alongside the existing imports from .baml for consistency; update the top-of-file imports to include flush_events with BamlRuntime and HostSpanManager, then remove the local "from .baml import flush_events" line from the flush() method so flush() uses the top-level symbol directly (unless a circular-import reason exists—if so, add a comment explaining that exception).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@baml_language/crates/baml_codegen_python/src/_askama/config.py`:
- Around line 9-19: The import path is wrong so the fallback no-op is always
used; change the import in the try from "baml.logging" to "baml_py.logging" so
baml_get_log_level and baml_set_log_level are imported from the real module
(keep the existing try/except fallback for pre-init contexts if desired),
ensuring the runtime logger updates work via the real baml_set_log_level
function.
In `@baml_language/crates/baml_codegen_python/src/objects.rs`:
- Around line 1136-1167: Intermediate package __init__.py files are being
created empty for function packages (in the loop over sub_groups that builds
sync_prefix_path/async_prefix_path), which breaks attribute access to child
packages; fix by collecting immediate child package names for each prefix (find
g in sub_groups where g.path starts with prefix and g.path.len() == prefix_len +
1), dedupe the child segment names, build an __init__ content string with "from
. import {child}" lines for each child, and insert that string into
out.entry(sync_prefix_path.join("__init__.py"))/.or_insert and similarly for
async_prefix_path so intermediate function packages mirror the child-import
behavior used by emit_type_tree.
- Around line 1070-1080: The current loop in the group.functions block is
emitting bare function stubs for companions (see fn_.companions, companion.name,
companion.render_args_str(Namespace::Other),
companion.return_type.render(Namespace::Other)), but companions are runtime
attributes on the parent function; change the generation in print_module_fn_pyi
context to emit attribute declarations on the parent function instead of
standalone defs — i.e. produce lines like "{parent_name}.{companion_name}:
Callable[{arg_types}, {ret_type}]" (import typing.Callable in the pyi output if
not present), using companion.render_args_str to build the Callable parameter
list and companion.return_type.render for the return type, so IDEs and
type-checkers see Parent.companion attributes and avoid name collisions.
- Around line 795-821: The generator currently treats presence of a generated
"baml" subpackage (has_baml_subpackage / imported_tops) by emitting "from .
import baml" and omitting the runtime "import baml", which causes runtime
symbols like Ty::BamlOptions (rendered as "baml.Options") to resolve to the
generated subpackage instead of the real runtime; change the header generation
(the header variable / type_file_header logic) so that when has_baml_subpackage
is true you still import the runtime under a distinct alias (e.g., "import baml
as _baml_runtime") and update all codegen places that render runtime references
(places that render Ty::BamlOptions or produce the literal "baml.*") to emit
"_baml_runtime.*" instead of "baml.*", while keeping the subpackage import "from
. import baml" for user types.
- Around line 1202-1215: The namespace __init__ generation misses imports for
immediate child groups; update get_types_namespace_init_py to accept the
immediate child names (for groups in sub_groups whose group.fs_path has this
group as prefix) and emit "from . import <child>" lines before the object
definitions, similar to the pattern in get_types_root_init_py; locate where
get_types_namespace_init_py is called in the loop over sub_groups (uses
group.fs_path and intermediate_dirs) and pass the computed immediate child list
so that non-root parent packages (e.g., group with fs_path ["foo"] when
["foo","bar"] exists) will include from . import bar in their generated content.
In `@baml_language/crates/baml_codegen_python/src/objects/render/class.rs`:
- Around line 182-185: The generated Python dataclass fields that are nullable
(e.g., bio, errors) are emitted as "bio: typing.Optional[str]" with no default,
which forces callers to pass the field; change the code that emits dataclass
field declarations in baml_codegen_python (class.rs) so that when a field's BAML
type is nullable/optional you append " = None" after the type (so it becomes
"bio: typing.Optional[str] = None"); update the generator branch that produces
typing.Optional[...] (where Profile is rendered) to detect nullable types and
emit the " = None" default for those fields.
In `@baml_language/crates/baml_codegen_tests/src/builders.rs`:
- Around line 80-97: The callable parser currently uses inner.find(']') and
params_str.split(',') which naively break on commas/brackets inside nested
generics or parens (e.g., map<string, int>); change it to a depth-aware parse:
find the matching closing ']' by walking characters and tracking
bracket/angle/paren depth (rather than inner.find), then split params_str by
commas at depth 0 (reuse the existing split_at_depth helper or implement an
equivalent that returns all splits) and trim each part before calling parse_ty;
keep building params as Vec<_> and ret as Box::new(parse_ty(...)) and return
Ty::Callable { params, ret } as before.
In `@baml_language/crates/baml_project/src/client_codegen.rs`:
- Around line 205-257: The code computes bare_alias_name but never uses it; fix
by using the bare name (bare_alias_name) for stream aliases when constructing
the codegen key and lookup so downstream Python codegen doesn't have to strip
"$stream": replace usages of alias.name when creating the cg::Name
(cg_name.name) and the QualifiedTypeName key with bare_alias_name (so cg::Name {
name: bare_alias_name, ... } and QualifiedTypeName::new(..., bare_alias_name)),
remove the pointless let _ = bare_alias_name binding, and keep is_stream to
choose the Namespace variant before inserting into pool via pool.insert(...) so
the stored key matches downstream expectations and recursive_aliases lookup uses
the bare name.
In `@baml_language/crates/bridge_python/Cargo.toml`:
- Line 9: Update stale module name references after renaming to "baml": in
baml_language/crates/bridge_python/src/errors.rs replace any
create_exception!(baml_py, ...) invocations with create_exception!(baml, ...) so
the macro targets the renamed crate, and in
baml_language/crates/bridge_python/src/types/collector.rs change the doc comment
import example from "from baml_py import Collector" to "from baml import
Collector".
In `@baml_language/crates/bridge_python/pyproject.toml`:
- Line 2: The pyproject.toml's package name was changed from "baml-py" to "baml"
(the name field in the bridge_python crate); either confirm that Boundary
owns/reserved the short PyPI name "baml" before keeping this change, or revert
the name field back to "baml-py" and update any packaging/metadata references in
the bridge_python crate (pyproject.toml name and any internal docs/release
scripts) to use the original distribution name to avoid squatting an unowned
PyPI name.
In `@baml_language/crates/bridge_python/python_src/baml/ctx_manager.py`:
- Around line 69-78: The ContextVar `self.ctx` is using a shared mutable default
and is being mutated in-place in __mgr, and elsewhere code replaces the whole
dict with only the current thread's manager (losing others); change to call
self.ctx.get({}) to obtain a fresh default, copy the returned dict (e.g.,
new_ctx = dict(ctx)) before any mutation in __mgr, insert/update the current
thread key via the copy, then call self.ctx.set(new_ctx) (or capture and use the
contextvars.Token from self.ctx.set to restore prior state where needed). Also
update code paths that currently replace the context dict (the places referenced
around lines 137 and 150) to merge or copy-and-set instead of assigning a
single-entry dict so other thread managers are preserved, and use the ContextVar
token when you need to restore previous context state.
In `@baml_language/rig_tests/crates/python_packages_and_namespaces/build.rs`:
- Around line 180-191: The rerun-if-changed loop only emits file watches for
existing files so new or deleted files in customizable_dir won't retrigger
builds; update the build script (build.rs) to also emit a watch for the
directory itself (use customizable_dir) in addition to per-file prints so Cargo
will rerun the build when entries are added/removed/modified; keep the existing
per-file loop but add a println that watches customizable_dir.display() (and
ensure customizable_dir exists before printing).
- Around line 60-67: The Windows branch currently calls
std::os::windows::fs::symlink_file(&src, &dst).unwrap_or_else(...) which panics
on ERROR_PRIVILEGE_NOT_HELD; change this to attempt symlink_file and on any
error fall back to std::fs::copy(&src, &dst) (preserving file_name/src/dst
variables), and surface a non-fatal warning (or log) instead of panicking;
implement this in the same scope where symlink_file is called (replace
unwrap_or_else with a match or if let Err(e) => { try
fs::copy(...).map_err(|copy_err| log both errors) } ) so the test-fixture
generator remains portable on non-elevated Windows.
---
Outside diff comments:
In `@baml_language/crates/baml_codegen_python/src/ty.rs`:
- Around line 16-20: The Name struct currently loses namespace path data and
Name::render only compares the enum variant, causing cross-directory types to
render as bare names; fix by preserving the full namespace path when converting
baml_codegen_types::Namespace into baml_codegen_python::Namespace (remove the
use of .. that drops path), extend Name to store the complete namespace.path and
thread that path through RenderCtx, update Name::render to compare full module
paths (not just the enum variant) to decide locality, and alter rendering so
references from different paths are emitted as qualified imports or
fully-qualified names (or added to imports) consistent with NamespaceGroup
partitioning; update or add unit tests that create types in different paths
referencing each other to assert correct qualified import/render behavior and
ensure cargo test --lib passes.
In `@baml_language/crates/bridge_python/python_src/baml_py/__init__.py`:
- Around line 5-17: The current relative import "from .baml import ..." will
fail because baml is a sibling package; change the import in __init__.py to use
an absolute import "from baml import AbortController, BamlRuntime, Collector as
_RustCollector, FunctionLog as _RustFunctionLog, FunctionResult,
HostSpanManager, LLMCall, Timing, Usage, flush_events, get_version" so the
symbols AbortController, BamlRuntime, _RustCollector, _RustFunctionLog,
FunctionResult, HostSpanManager, LLMCall, Timing, Usage, flush_events and
get_version are imported from the sibling package correctly.
---
Nitpick comments:
In `@baml_language/crates/baml_codegen_python/src/objects/render/type_alias.rs`:
- Around line 152-179: The test type_alias_recursive_self_ref relies on parser
precedence via ty("int | RecursiveAlias[]"); instead, build the resolved type
explicitly so the test asserts quoting behavior regardless of parser changes:
replace the resolves_to: ty("int | RecursiveAlias[]") in the CgTypeAlias passed
to TypeAlias::from_codegen_types with an explicit Ty constructed as a Union of
int and List(self_ref) (e.g., Ty::Union([Ty::Int,
Ty::List(Ty::Ref("RecursiveAlias"))]) using the test Ty constructors), and keep
the rest of the test (including the assertion for quoted "RecursiveAlias")
unchanged so it verifies self-ref quoting independent of parser quirks.
In `@baml_language/crates/bridge_python/python_src/baml_py/ctx_manager.py`:
- Line 192: The local import of flush_events inside the flush() method should be
moved to the module top alongside the existing imports from .baml for
consistency; update the top-of-file imports to include flush_events with
BamlRuntime and HostSpanManager, then remove the local "from .baml import
flush_events" line from the flush() method so flush() uses the top-level symbol
directly (unless a circular-import reason exists—if so, add a comment explaining
that exception).
In
`@baml_language/rig_tests/crates/python_packages_and_namespaces/customizable/test_main.py`:
- Around line 32-34: The test named test_stream_types_mirrors_structure
currently only checks importability of baml_client.baml_stream_types; update it
to either be renamed to test_stream_types_importable to reflect its current
behavior or extend it to assert the actual mirrored structure by verifying
expected attributes and nested members on the imported module (e.g., check that
baml_client.baml_stream_types has attributes like foo and that foo.Sentiment
exists, and that nested paths such as
baml_client.baml_stream_types.other.foo.Address are present) — modify the
test_stream_types_mirrors_structure function to perform attribute checks (using
hasattr or attribute access assertions) against those specific symbols to ensure
the tree mirrors the expected structure.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: ddda5ed8-6cb7-45c7-bb08-45446d6b6658
⛔ Files ignored due to path filters (1)
baml_language/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (49)
baml_language/crates/baml_cli/src/generate.rsbaml_language/crates/baml_codegen_python/src/_askama/config.pybaml_language/crates/baml_codegen_python/src/_askama/globals.pybaml_language/crates/baml_codegen_python/src/_askama/runtime.pybaml_language/crates/baml_codegen_python/src/lib.rsbaml_language/crates/baml_codegen_python/src/objects.rsbaml_language/crates/baml_codegen_python/src/objects/render/class.rsbaml_language/crates/baml_codegen_python/src/objects/render/function.rsbaml_language/crates/baml_codegen_python/src/objects/render/type_alias.rsbaml_language/crates/baml_codegen_python/src/ty.rsbaml_language/crates/baml_codegen_tests/Cargo.tomlbaml_language/crates/baml_codegen_tests/src/builders.rsbaml_language/crates/baml_codegen_tests/src/lib.rsbaml_language/crates/baml_codegen_types/Cargo.tomlbaml_language/crates/baml_codegen_types/src/objects.rsbaml_language/crates/baml_codegen_types/src/ty.rsbaml_language/crates/baml_project/src/client_codegen.rsbaml_language/crates/bridge_python/Cargo.tomlbaml_language/crates/bridge_python/pyproject.tomlbaml_language/crates/bridge_python/python_src/baml/__init__.pybaml_language/crates/bridge_python/python_src/baml/ctx_manager.pybaml_language/crates/bridge_python/python_src/baml/errors.pybaml_language/crates/bridge_python/python_src/baml/proto.pybaml_language/crates/bridge_python/python_src/baml_py/__init__.pybaml_language/crates/bridge_python/python_src/baml_py/ctx_manager.pybaml_language/crates/bridge_python/python_src/baml_py/errors.pybaml_language/crates/bridge_python/python_src/baml_py/proto.pybaml_language/crates/bridge_python/src/lib.rsbaml_language/crates/bridge_python/tests/conftest.pybaml_language/crates/bridge_python/tests/test_collector.pybaml_language/crates/bridge_python/tests/test_engine.pybaml_language/crates/bridge_python/tests/test_tracing.pybaml_language/rig_tests/crate_templates/python/customizable/test_main.py.templatebaml_language/rig_tests/crates/python_companion_functions/Cargo.tomlbaml_language/rig_tests/crates/python_companion_functions/build.rsbaml_language/rig_tests/crates/python_companion_functions/customizable/test_main.pybaml_language/rig_tests/crates/python_companion_functions/src/lib.rsbaml_language/rig_tests/crates/python_full_type_coverage/Cargo.tomlbaml_language/rig_tests/crates/python_full_type_coverage/build.rsbaml_language/rig_tests/crates/python_full_type_coverage/customizable/test_main.pybaml_language/rig_tests/crates/python_full_type_coverage/src/lib.rsbaml_language/rig_tests/crates/python_packages_and_namespaces/Cargo.tomlbaml_language/rig_tests/crates/python_packages_and_namespaces/build.rsbaml_language/rig_tests/crates/python_packages_and_namespaces/customizable/test_main.pybaml_language/rig_tests/crates/python_packages_and_namespaces/src/lib.rstest_root/main.pytest_root/root/__init__.pytest_root/root/bar.pytest_root/root/foo.py
| try: | ||
| from baml.logging import ( | ||
| get_log_level as baml_get_log_level, | ||
| set_log_level as baml_set_log_level, | ||
| ) | ||
| except ImportError: | ||
| def baml_get_log_level(): # type: ignore[misc] | ||
| return os.environ.get("BAML_LOG", "INFO") | ||
|
|
||
| def baml_set_log_level(level: str) -> None: # type: ignore[misc] | ||
| pass |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether baml.logging module is expected to always exist in generated output
fd -t f 'logging.py' | head -50
rg -nP 'from baml(\.logging)?\s+import' --type=py
rg -nP 'baml_set_log_level|baml_get_log_level' --type=pyRepository: BoundaryML/baml
Length of output: 12172
🏁 Script executed:
# Check the actual logging.py implementation
cat -n engine/language_client_python/python_src/baml_py/logging.pyRepository: BoundaryML/baml
Length of output: 469
🏁 Script executed:
# Examine the template to see how these functions are used
cat -n engine/generators/languages/python/src/_templates/config.pyRepository: BoundaryML/baml
Length of output: 3153
🏁 Script executed:
# Check if there are any imports or setup that ensure baml.logging is available
rg -n "baml.logging|baml_py" --type=py -A 3 -B 3 | head -100Repository: BoundaryML/baml
Length of output: 9057
🏁 Script executed:
cat -n baml_language/crates/baml_codegen_python/src/_askama/config.py | head -80Repository: BoundaryML/baml
Length of output: 2891
🏁 Script executed:
# Check if there's a baml package __init__ that re-exports from baml_py
find . -name "__init__.py" -path "*/baml/__init__.py" -o -name "__init__.py" -path "*/baml_py/__init__.py" | head -10Repository: BoundaryML/baml
Length of output: 254
🏁 Script executed:
# Look for baml package (different from baml_py)
fd -t f "__init__.py" | xargs grep -l "baml.logging\|from.*baml.*import" | grep -v generated | grep -v test | head -20Repository: BoundaryML/baml
Length of output: 313
🏁 Script executed:
# Check what the bridge_python baml package exports
cat -n baml_language/crates/bridge_python/python_src/baml/__init__.pyRepository: BoundaryML/baml
Length of output: 3912
🏁 Script executed:
# Check what's in baml_py
cat -n baml_language/crates/bridge_python/python_src/baml_py/__init__.py | head -50Repository: BoundaryML/baml
Length of output: 1510
🏁 Script executed:
# Look for any logging module in bridge_python
find baml_language/crates/bridge_python -name "*logging*"Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check if baml.logging exists somewhere
fd -t f "logging.py" | grep -v test | grep -v generatedRepository: BoundaryML/baml
Length of output: 118
🏁 Script executed:
# Search for any baml.logging module or import
rg "import.*baml\.logging\|from baml\.logging" --type=py | grep -v test | grep -v generatedRepository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check what baml_py exports for logging
cat -n engine/language_client_python/python_src/baml_py/__init__.py | grep -A 5 -B 5 loggingRepository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Understand the package structure - is there a baml package that re-exports baml_py?
find . -name "setup.py" -o -name "pyproject.toml" | grep -v test | grep -v generated | head -10Repository: BoundaryML/baml
Length of output: 180
🏁 Script executed:
# Check the engine's pyproject.toml for package info
cat engine/language_client_python/pyproject.tomlRepository: BoundaryML/baml
Length of output: 1341
🏁 Script executed:
# Check the bridge_python pyproject.toml
cat baml_language/crates/bridge_python/pyproject.toml | head -100Repository: BoundaryML/baml
Length of output: 659
🏁 Script executed:
# Look at what the actual generated config.py files are importing
grep -A 5 "try:" engine/generators/languages/python/generated_tests/dynamic_types/baml_client/config.py | head -20Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check if baml package exports logging
cat baml_language/crates/bridge_python/python_src/baml/__init__.py | grep loggingRepository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check what's available in baml package
ls -la baml_language/crates/bridge_python/python_src/baml/ | grep -v ".so"Repository: BoundaryML/baml
Length of output: 466
🏁 Script executed:
# Search for where baml.logging might be defined
rg "logging" baml_language/crates/bridge_python/python_src/baml/ --type pyRepository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check the template file more carefully - line 9-10 specifically
cat -n baml_language/crates/baml_codegen_python/src/_askama/config.py | sed -n '9,20p'Repository: BoundaryML/baml
Length of output: 481
🏁 Script executed:
# Check if there are multiple config.py templates
find . -name "config.py" -path "*template*" -o -name "config.py" -path "*askama*" | grep -v generated | grep -v testRepository: BoundaryML/baml
Length of output: 185
🏁 Script executed:
# Verify which template is actually being used by checking the code generation
rg "config.py" baml_language/crates/baml_codegen_python/src/ --type rs | head -20Repository: BoundaryML/baml
Length of output: 85
🏁 Script executed:
# Check if the askama template is the one being used or if there's another
ls -la baml_language/crates/baml_codegen_python/src/_askama/Repository: BoundaryML/baml
Length of output: 479
🏁 Script executed:
# Check what baml module actually exports - does it have logging submodule?
rg "logging" baml_language/crates/bridge_python/python_src/baml/ -t pyRepository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Verify that baml.logging doesn't exist by checking imports
python3 << 'EOF'
import sys
sys.path.insert(0, 'baml_language/crates/bridge_python/python_src')
try:
from baml import logging
print("baml.logging exists")
except (ImportError, AttributeError) as e:
print(f"baml.logging does NOT exist: {e}")
# Check what baml exports
try:
import baml
print("Available in baml:", dir(baml))
except ImportError as e:
print(f"Cannot import baml: {e}")
EOFRepository: BoundaryML/baml
Length of output: 163
🏁 Script executed:
# Compare the two template files side by side
echo "=== _askama config.py lines 9-20 ==="
sed -n '9,20p' baml_language/crates/baml_codegen_python/src/_askama/config.py
echo ""
echo "=== _templates config.py lines 9-20 ==="
sed -n '9,20p' engine/generators/languages/python/src/_templates/config.pyRepository: BoundaryML/baml
Length of output: 851
Import path is incorrect — should be baml_py.logging, not baml.logging.
The module baml.logging does not exist, so the try-except will always trigger the fallback. When it does, baml_set_log_level becomes a no-op, preventing any runtime logger updates. The intended pattern (as shown in the engine template) imports from baml_py.logging, which exists and is properly configured. Fix the import on line 10 to import from baml_py.logging instead, or ensure the fallback is only used in pre-initialization contexts where no runtime has been constructed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@baml_language/crates/baml_codegen_python/src/_askama/config.py` around lines
9 - 19, The import path is wrong so the fallback no-op is always used; change
the import in the try from "baml.logging" to "baml_py.logging" so
baml_get_log_level and baml_set_log_level are imported from the real module
(keep the existing try/except fallback for pre-init contexts if desired),
ensuring the runtime logger updates work via the real baml_set_log_level
function.
| // Build the file header. If there's a subpackage named "baml", we skip | ||
| // the runtime `import baml` stub because the subpackage would shadow it. | ||
| let has_baml_subpackage = imported_tops.contains("baml"); | ||
| let header = if has_baml_subpackage { | ||
| // No `import baml` runtime stub — the baml subpackage takes precedence. | ||
| r#"from __future__ import annotations | ||
|
|
||
| import dataclasses | ||
| import typing | ||
| import typing_extensions | ||
| from enum import Enum"# | ||
| .to_string() | ||
| } else { | ||
| type_file_header(ns) | ||
| }; | ||
|
|
||
| let mut lines: Vec<String> = vec![header]; | ||
|
|
||
| // Submodule imports come next — before any class/enum/alias definitions | ||
| // so ruff E402 (module-level import not at top) is not triggered. | ||
| if !imported_tops.is_empty() { | ||
| let import_lines: Vec<String> = imported_tops | ||
| .iter() | ||
| .map(|top| format!("from . import {top}")) | ||
| .collect(); | ||
| lines.push(import_lines.join("\n")); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate and examine the file and surrounding context
cd baml_language/crates/baml_codegen_python/src && \
wc -l objects.rs && \
echo "=== Lines 790-830 ===" && \
sed -n '790,830p' objects.rsRepository: BoundaryML/baml
Length of output: 1541
🏁 Script executed:
# Find the type_file_header function definition
rg -n "fn type_file_header" baml_language/crates/baml_codegen_python/src/Repository: BoundaryML/baml
Length of output: 169
🏁 Script executed:
# Search for type_file_header implementation to see what it includes
rg -A 20 "fn type_file_header" baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 753
🏁 Script executed:
# Search for baml.* references being rendered in this file (e.g., baml.Image, baml_runtime)
rg "baml\\..*\"" baml_language/crates/baml_codegen_python/src/objects.rs | head -20Repository: BoundaryML/baml
Length of output: 137
🏁 Script executed:
# Check what imported_tops contains and how subpackages are named
rg -B 10 "imported_tops.contains" baml_language/crates/baml_codegen_python/src/objects.rs | head -40Repository: BoundaryML/baml
Length of output: 660
🏁 Script executed:
# Check what types render baml.* references (e.g., in print or rendering methods)
rg -n "fn print" baml_language/crates/baml_codegen_python/src/objects.rs | head -20Repository: BoundaryML/baml
Length of output: 436
🏁 Script executed:
# Search for baml.Image, baml.Audio, baml.Options in type expressions (not just docstrings)
rg "baml\." baml_language/crates/baml_codegen_python/src/objects.rs | grep -v "docstring\|//\|See\|#" | head -30Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check if there's a scenario where types/enums/aliases are placed in a group with fs_path=["baml"]
rg -n "fs_path.*baml\|baml.*fs_path" baml_language/crates/baml_codegen_python/src/ -A 3Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Understand the context: are there any type definitions that would be placed in a baml/ subdir?
# Check the test files or integration tests for generated output
fd -t f "\.py$" integ-tests --exec grep -l "from __future__ import annotations" {} \; 2>/dev/null | head -5Repository: BoundaryML/baml
Length of output: 230
🏁 Script executed:
# Look at the actual generated code from tests to see if baml subpackage exists and what it contains
fd -t d "baml_types" integ-tests --type d 2>/dev/null | head -5Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Look at how TypeAlias objects print themselves - do they render baml.X?
rg -A 10 "impl.*TypeAlias" baml_language/crates/baml_codegen_python/src/objects.rs | head -30Repository: BoundaryML/baml
Length of output: 864
🏁 Script executed:
# Find the TypeAlias::print method
ast-grep --pattern 'impl TypeAlias {
$$$
fn print($_, $_) {
$$$
}
$$$
}'Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Search more broadly for type rendering - check how types like Image are referenced
rg -n "class.*\|enum.*\|type.*=" baml_language/crates/baml_codegen_python/src/objects.rs | head -20Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check actual generated baml_types from a test/integration test to see structure
find integ-tests -type d -name "baml_types" 2>/dev/null | head -2 | xargs -I {} find {} -type f -name "*.py" | head -10Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Look at generated root types to understand what gets placed at root vs in subpackages
rg -B 5 -A 5 "root_group\|root.objects" baml_language/crates/baml_codegen_python/src/objects.rs | head -50Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Find where Ty enum is defined and how it renders (especially for built-in types like Image, Options)
rg -A 30 "^pub.*enum Ty\|^enum Ty" baml_language/crates/baml_codegen_python/src/objects.rs | head -50Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Search for Image, Audio, Video rendering - these are the baml.* types mentioned
rg -n "Image\|Audio\|Video\|Options" baml_language/crates/baml_codegen_python/src/objects.rs | head -30Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Find where these types get their representation - search for Display/render implementations
rg -B 3 -A 10 "impl.*Display\|fn render\|fn fmt" baml_language/crates/baml_codegen_python/src/objects.rs | head -60Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Look at the actual type hierarchy to understand how baml.Image etc are rendered
cd baml_language/crates/baml_codegen_python/src && grep -n "render\|Image\|display" objects.rs | head -40Repository: BoundaryML/baml
Length of output: 2183
🏁 Script executed:
# Check the type system - look at baml_codegen_types to understand what Image, Options etc are
find baml_language/crates -name "*.rs" -path "*baml_codegen_types*" | head -5Repository: BoundaryML/baml
Length of output: 268
🏁 Script executed:
# Search for where built-in types like Image resolve to baml module
rg -n "baml\.Image\|baml\.Options\|baml\.Audio" baml_language/crates/ --type rust | head -20Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Look at baml_codegen_types ty.rs to see how Image and built-in types are rendered
cat baml_language/crates/baml_codegen_types/src/ty.rs | head -200Repository: BoundaryML/baml
Length of output: 5889
🏁 Script executed:
# Search for BuiltinType or similar enum in ty.rs
rg -n "Image\|Audio\|enum.*Builtin\|struct.*Image" baml_language/crates/baml_codegen_types/src/ty.rs | head -30Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Look at how Ty renders - specifically search for render implementations
rg -A 50 "impl.*Ty" baml_language/crates/baml_codegen_types/src/ty.rs | grep -A 20 "fn render\|Image\|baml\." | head -60Repository: BoundaryML/baml
Length of output: 1218
🏁 Script executed:
# Search in the entire codebase for where Image type gets rendered with baml prefix
rg "\"baml\\.Image\"|'baml\\.Image'" baml_language/crates/ --type rustRepository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check if there's a scenario where a user would create a top-level type or class named "baml"
# This would create a baml/ subpackage
rg -n "class baml\|type baml\|enum baml" integ-tests --type baml -iRepository: BoundaryML/baml
Length of output: 87
🏁 Script executed:
# Look at how types are organized in groups and assigned to fs_path
rg -B 5 -A 5 "fs_path\|GroupPath" baml_language/crates/baml_codegen_python/src/objects.rs | head -100Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Look at where groups are created and how they get their fs_path
rg -B 10 "struct.*Group\|pub.*struct.*Object" baml_language/crates/baml_codegen_python/src/objects.rs | head -80Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Find where fs_path is assigned - look for how groups end up in subpackages
rg -n "fs_path" baml_language/crates/baml_codegen_python/src/objects.rs | head -40Repository: BoundaryML/baml
Length of output: 828
🏁 Script executed:
# Search for where groups are created from VIR - the codegen input
rg -B 5 "all_groups\|Group {" baml_language/crates/baml_codegen_python/src/objects.rs | head -60Repository: BoundaryML/baml
Length of output: 160
🏁 Script executed:
# Look at test integration - are there actual cases where baml subpackage exists?
find integ-tests -type d -name "baml" | head -10Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check if there's a user-defined type/class named "baml" that would create the subpackage
find integ-tests -name "*.baml" -exec grep -l "^class baml\|^enum baml\|^type baml" {} \; 2>/dev/null | head -5Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Look at the actual structure - if a user has a namespace with a "baml" type, does it create baml/ dir?
rg "namespace.*baml\|baml.*namespace" integ-tests/baml_src --type baml 2>/dev/null | head -20Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Look at the Group struct definition and how fs_path gets populated
rg -B 5 -A 10 "pub.*struct.*Group" baml_language/crates/baml_codegen_python/src/objects.rs | head -40Repository: BoundaryML/baml
Length of output: 1275
🏁 Script executed:
# Search for where groups are created - look for the codegen_input structure
rg -n "codegen_input\|CodegenInput" baml_language/crates/baml_codegen_python/src/objects.rs | head -20Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check if there's any documentation or test showing a "baml" namespace
find baml_language/crates/baml_codegen_python -name "*.rs" -exec grep -l "baml.*namespace\|namespace.*baml" {} \;Repository: BoundaryML/baml
Length of output: 165
🏁 Script executed:
# Look at how a user would create a namespace or class - can they name it "baml"?
rg "namespace\|class\|type" integ-tests/baml_src --type baml --max-count=50 | head -30Repository: BoundaryML/baml
Length of output: 87
🏁 Script executed:
# Check if there are any constraints on naming types/classes - can "baml" be used?
rg "reserved.*name\|baml.*reserved" baml_language/crates/ --type rust | head -20Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Search for where fs_path comes from in the codegen input structure
rg -B 10 "fs_path.*vec\|vec.*fs_path" baml_language/crates/baml_codegen_python/src/ --type rust | head -40Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check if "baml" is a reserved name in the compiler
rg "reserved|keyword" baml_language/crates/baml_parser2 --type rust -i | grep -i baml | head -20Repository: BoundaryML/baml
Length of output: 132
🏁 Script executed:
# Look for where NamespaceGroup gets created - trace back to VIR input
rg -B 20 "fs_path.*vec\|NamespaceGroup" baml_language/crates/baml_codegen_python/src/objects.rs | grep -A 10 "pub fn.*CodegenInput\|impl.*CodegenInput" | head -40Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check the render implementation for Ty to understand how baml.Options gets used in actual rendered code
rg -A 30 "impl.*Display.*Ty\|fn fmt.*Ty" baml_language/crates/baml_codegen_types/src/ty.rs | head -80Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check if there's a test case or example with a "baml" namespace
find baml_language/crates/baml_codegen_python -name "*.rs" -type f -exec grep -l "test.*baml.*namespace\|baml.*shadow" {} \;Repository: BoundaryML/baml
Length of output: 114
🏁 Script executed:
# Look at the entire codegen flow - where does fs_path originate from?
rg -n "NamespaceGroup" baml_language/crates/baml_codegen_python/src/objects.rs | head -20Repository: BoundaryML/baml
Length of output: 754
🏁 Script executed:
# Look at how NamespaceGroup is created from codegen input - where does the namespace path come from?
sed -n '182,280p' baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 3928
🏁 Script executed:
# Check the from_codegen_input method to understand where fs_path originates
rg -B 5 -A 50 "fn from_codegen_input" baml_language/crates/baml_codegen_python/src/objects.rs | head -100Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Look at what RenderCtx does - especially for type aliases and how baml.* types are rendered
rg -B 5 -A 20 "struct RenderCtx\|impl RenderCtx" baml_language/crates/baml_codegen_python/src/objects.rs | head -80Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check the actual Ty::render implementation to see how baml.Options gets rendered into Python
rg -A 100 "impl.*Ty.*render\|fn render.*Ty" baml_language/crates/baml_codegen_types/src/ty.rs | head -150Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check how Ty::Media is rendered in Python - is it rendered as baml.Image?
rg -B 5 -A 30 "MediaKind\|Media.*render\|fn.*render.*Media" baml_language/crates/baml_codegen_types/src/ty.rs | head -100Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Look for the complete render implementation in ty.rs, specifically for Media types
sed -n '1,400p' baml_language/crates/baml_codegen_types/src/ty.rs | tail -100Repository: BoundaryML/baml
Length of output: 4210
🏁 Script executed:
# Find where Media/Image types are actually rendered in Python output
rg -n "media\|Image" baml_language/crates/baml_codegen_python/src/ --type rust -i | head -30Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check if there's a render_python method or similar for Ty
rg "render_python\|to_python_type" baml_language/crates/baml_codegen_types/src/ty.rsRepository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Look at the complete ty.rs file to understand the entire Ty rendering flow
wc -l baml_language/crates/baml_codegen_types/src/ty.rs && \
echo "=== Last 200 lines ===" && \
tail -200 baml_language/crates/baml_codegen_types/src/ty.rsRepository: BoundaryML/baml
Length of output: 7410
🏁 Script executed:
# Search for how BamlOptions is used and rendered in actual generated code
rg -n "BamlOptions" baml_language/crates/baml_codegen_python/src/ --type rustRepository: BoundaryML/baml
Length of output: 526
🏁 Script executed:
# Check if there's a render context that transforms baml.Options or baml.Image differently
rg -B 10 -A 30 "struct RenderCtx" baml_language/crates/baml_codegen_python/src/objects.rs | head -80Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Look for how Ty is rendered in actual code generation (not just Display)
rg "fn render" baml_language/crates/baml_codegen_python/src/ty.rs | head -20Repository: BoundaryML/baml
Length of output: 369
🏁 Script executed:
# Check if there's a render implementation for Ty that differs from Display
cat baml_language/crates/baml_codegen_python/src/ty.rs | head -200Repository: BoundaryML/baml
Length of output: 5501
🏁 Script executed:
# Look at the full render implementation in baml_codegen_python/src/ty.rs
wc -l baml_language/crates/baml_codegen_python/src/ty.rs && \
sed -n '300,400p' baml_language/crates/baml_codegen_python/src/ty.rsRepository: BoundaryML/baml
Length of output: 1661
🏁 Script executed:
# Check if there are constraints on naming - can a user actually create a "baml" namespace?
rg "baml.*reserved\|reserved.*name\|keyword" baml_language/crates --type rust | head -20Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Search for tests that might show a baml namespace being created
find baml_language/crates/baml_codegen_python -name "*.rs" -type f -exec grep -l "\"baml\"\|baml.*namespace" {} \;Repository: BoundaryML/baml
Length of output: 165
🏁 Script executed:
# Final check: verify that render actually uses Ty enum and renders baml.Options in context
sed -n '320,350p' baml_language/crates/baml_codegen_python/src/ty.rsRepository: BoundaryML/baml
Length of output: 827
🏁 Script executed:
# Final verification: confirm generated code at root level can reference baml.Options
rg -B 5 -A 10 "BamlOptions\|baml\.Options" baml_language/crates/baml_codegen_python/src/ty.rsRepository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check if there are any tests or fixtures showing the "baml" namespace scenario
find baml_language/crates/baml_codegen_python -name "*.rs" -type f -exec grep -l "test.*baml.*namespace\|\"baml\".*path\|path.*baml" {} \;Repository: BoundaryML/baml
Length of output: 114
🏁 Script executed:
# Verify: does the code actually need to handle User-defined "baml" namespace, or is this theoretical?
rg -C 5 "NsLayout::Baml" baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 443
🏁 Script executed:
# Verify this is actually a problem: can type aliases with baml.Options appear at root?
rg -B 5 -A 10 "get_types_namespace_init_py" baml_language/crates/baml_codegen_python/src/objects.rs | head -40Repository: BoundaryML/baml
Length of output: 1296
🏁 Script executed:
# Check if root group (empty path) can contain types that use baml.Options
sed -n '745,800p' baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 2258
🏁 Script executed:
# Confirm the issue: check if root objects can have types using baml.Options
sed -n '820,860p' baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 1243
🏁 Script executed:
# Look for any handling of BamlOptions or baml prefix in root init rendering
rg -C 3 "BamlOptions\|baml\.Options" baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 41
Do not let a generated baml subpackage shadow runtime baml references.
When a user defines types in a namespace starting with "baml" (e.g., class baml.MyType), the codegen creates a baml/ subpackage directory. This code detects that condition via has_baml_subpackage = imported_tops.contains("baml") and skips the runtime import baml statement, emitting from . import baml instead.
Any root-level type or alias using Ty::BamlOptions renders as the literal string "baml.Options". With the generated subpackage imported as baml, that reference now resolves to the generated subpackage, not the runtime module—which does not expose an Options attribute, causing attribute errors.
Use a distinct runtime alias (e.g., import baml as _baml_runtime) in generated code and render runtime symbols through that alias so generated baml subpackages can coexist safely.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@baml_language/crates/baml_codegen_python/src/objects.rs` around lines 795 -
821, The generator currently treats presence of a generated "baml" subpackage
(has_baml_subpackage / imported_tops) by emitting "from . import baml" and
omitting the runtime "import baml", which causes runtime symbols like
Ty::BamlOptions (rendered as "baml.Options") to resolve to the generated
subpackage instead of the real runtime; change the header generation (the header
variable / type_file_header logic) so that when has_baml_subpackage is true you
still import the runtime under a distinct alias (e.g., "import baml as
_baml_runtime") and update all codegen places that render runtime references
(places that render Ty::BamlOptions or produce the literal "baml.*") to emit
"_baml_runtime.*" instead of "baml.*", while keeping the subpackage import "from
. import baml" for user types.
| for fn_ in &group.functions { | ||
| parts.push(fn_.print_module_fn_pyi()); | ||
| // Stubs for companion attributes (as bare function stubs). | ||
| for companion in &fn_.companions { | ||
| parts.push(format!( | ||
| "def {cname}{args} -> {ret}: ...", | ||
| cname = companion.name, | ||
| args = companion.render_args_str(Namespace::Other), | ||
| ret = companion.return_type.render(Namespace::Other), | ||
| )); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd baml_language && find . -type f -name "objects.rs" | head -20Repository: BoundaryML/baml
Length of output: 185
🏁 Script executed:
cd baml_language && wc -l crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 105
🏁 Script executed:
cd baml_language && sed -n '1065,1090p' crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 936
🏁 Script executed:
cd baml_language && grep -rn "Parent\.companion" --include="*.rs" --include="*.py" | head -30Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
cd baml_language && grep -rn "companions" crates/baml_codegen_python/src/ -A 2 -B 2 | head -50Repository: BoundaryML/baml
Length of output: 3126
🏁 Script executed:
cd baml_language && sed -n '930,960p' crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 1580
🏁 Script executed:
cd baml_language && sed -n '1020,1050p' crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 1609
🏁 Script executed:
cd baml_language && grep -rn "def print_module_fn_pyi" crates/baml_codegen_python/src/objects.rs -A 20 | head -60Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
cd baml_language && find . -type f \( -name "*test*" -o -name "*tests*" \) -path "*/codegen*" | head -20Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
cd baml_language && sed -n '950,960p' crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 753
🏁 Script executed:
cd baml_language && grep -rn "type: ignore\[attr-defined\]" crates/baml_codegen_python/src/Repository: BoundaryML/baml
Length of output: 327
🏁 Script executed:
cd baml_language && grep -rn "\.pyi" crates/baml_codegen_python/src/ -B 3 -A 3 | head -80Repository: BoundaryML/baml
Length of output: 2067
🏁 Script executed:
cd baml_language && find . -type f -name "*.pyi" | head -10Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
cd baml_language && grep -rn "print_module_fn_pyi" crates/baml_codegen_python/src/objects.rs -B 5 -A 30 | head -100Repository: BoundaryML/baml
Length of output: 3060
🏁 Script executed:
cd baml_language && sed -n '558,570p' crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 439
🏁 Script executed:
cd baml_language && find . -type f -name "function.rs" | xargs grep -l "print_module_fn_pyi"Repository: BoundaryML/baml
Length of output: 118
🏁 Script executed:
cd baml_language && grep -rn "print_module_fn_pyi" . --include="*.rs" | grep -v "Binary"Repository: BoundaryML/baml
Length of output: 539
🏁 Script executed:
cd baml_language && find . -path "*/baml_codegen_types/*" -name "*.rs" | head -10Repository: BoundaryML/baml
Length of output: 220
🏁 Script executed:
cd baml_language && grep -rn "fn print_module_fn_pyi" . --include="*.rs"Repository: BoundaryML/baml
Length of output: 325
🏁 Script executed:
cd baml_language && sed -n '1,100p' crates/baml_codegen_python/src/objects/render/function.rsRepository: BoundaryML/baml
Length of output: 4237
🏁 Script executed:
cd baml_language && wc -l crates/baml_codegen_python/src/objects/render/function.rsRepository: BoundaryML/baml
Length of output: 120
🏁 Script executed:
cd baml_language && grep -n "pub fn print_module_fn_pyi" crates/baml_codegen_python/src/objects/render/function.rs -A 60 | head -80Repository: BoundaryML/baml
Length of output: 2520
🏁 Script executed:
cd baml_language && grep -rn "Parent\." --include="*.py" crates/baml_codegen_python/Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
cd baml_language && grep -rn "companion" crates/baml_codegen_python/src/objects.rs -B 2 -A 2 | grep -E "(name|collision|attribute)" | head -20Repository: BoundaryML/baml
Length of output: 1302
🏁 Script executed:
cd baml_language && find . -path "*test*" -name "*.rs" -o -name "*.py" | xargs grep -l "companion" 2>/dev/null | head -10Repository: BoundaryML/baml
Length of output: 356
🏁 Script executed:
cd baml_language && python3 << 'EOF'
# Check if there's a way to see what the output .pyi looks like
import os
import subprocess
# Search for example or snapshot test files
result = subprocess.run(['find', '.', '-name', '*.snap', '-o', '-name', '*expected*'],
capture_output=True, text=True)
snap_files = result.stdout.strip().split('\n')[:20]
for f in snap_files:
if f and 'companion' in open(f).read().lower() if os.path.isfile(f) else False:
print(f"Found in: {f}")
EOFRepository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
cd baml_language && find . -type f -name "*.py" -path "*/tests/*" | head -10Repository: BoundaryML/baml
Length of output: 517
🏁 Script executed:
cd baml_language && grep -rn "\.companion" . --include="*.py" --include="*.rs" | head -20Repository: BoundaryML/baml
Length of output: 1237
🏁 Script executed:
cd baml_language && cat ./rig_tests/crates/python_companion_functions/customizable/test_main.pyRepository: BoundaryML/baml
Length of output: 1169
🏁 Script executed:
cd baml_language && ls -la ./rig_tests/crates/python_companion_functions/Repository: BoundaryML/baml
Length of output: 409
🏁 Script executed:
cd baml_language && cat ./rig_tests/crates/python_companion_functions/build.rsRepository: BoundaryML/baml
Length of output: 6056
🏁 Script executed:
cd baml_language && find ./rig_tests/crates/python_companion_functions -type f -name "*.pyi" -o -name "*.py" | head -20Repository: BoundaryML/baml
Length of output: 130
🏁 Script executed:
cd baml_language && grep -rn "build_request\|parse" rig_tests/crates/python_companion_functions/ --include="*.py" | head -30Repository: BoundaryML/baml
Length of output: 430
🏁 Script executed:
cd baml_language && grep -rn "print_module_fn_pyi" crates/baml_codegen_python/src/objects/render/function.rs -B 5 -A 5Repository: BoundaryML/baml
Length of output: 497
🏁 Script executed:
cd baml_language && grep -rn "def {{function_.name}}" crates/baml_codegen_python/src/objects/render/function.rsRepository: BoundaryML/baml
Length of output: 593
🏁 Script executed:
cd baml_language && find . -path "*/python_companion_functions/generated/*" -name "*.pyi" 2>/dev/null | head -5Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
cd baml_language && find . -type d -name "generated" -path "*/python_companion_functions/*" 2>/dev/nullRepository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
cd baml_language && ls -la rig_tests/crates/python_companion_functions/Repository: BoundaryML/baml
Length of output: 409
🏁 Script executed:
cd baml_language && find . -path "*tests*" -name "*.pyi" | head -10Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
cd baml_language && grep -rn "test_type_hints_resolve" rig_tests/ -A 10Repository: BoundaryML/baml
Length of output: 507
🏁 Script executed:
cd baml_language && grep -rn "fixture" crates/baml_codegen_tests/src/builders.rs | head -10Repository: BoundaryML/baml
Length of output: 674
🏁 Script executed:
cd baml_language && grep -rn "companion_functions" crates/baml_codegen_tests/src/ -A 30 | head -80Repository: BoundaryML/baml
Length of output: 2582
🏁 Script executed:
cd baml_language && sed -n '237,252p' crates/baml_codegen_tests/src/lib.rsRepository: BoundaryML/baml
Length of output: 734
🏁 Script executed:
cd baml_language && grep -rn "get_baml_sync_init_pyi\|get_baml_async_init_pyi" crates/baml_codegen_python/src/ -B 2 -A 15 | head -60Repository: BoundaryML/baml
Length of output: 3033
🏁 Script executed:
cd baml_language && python3 << 'EOF'
# Simulate what the current code would produce for the .pyi file
# Based on lines 1070-1080
functions_and_companions = [
("ExtractResume", [("build_request", "string -> string"), ("parse", "string -> Resume")]),
("foo.ClassifySentiment", [("build_request", "string -> string")])
]
print("Current .pyi output (bare function stubs):")
for fn_name, companions in functions_and_companions:
print(f"def {fn_name}(...) -> ...: ...")
for cname, sig in companions:
print(f"def {cname}{sig}: ... # <-- bare stub, not attached to parent")
print("\n---\n")
print("What type checkers would see:")
print("Module-level functions: ExtractResume, build_request, parse, foo, ClassifySentiment")
print("Attributes on functions: None (type: ignore[attr-defined] silences the error)")
EOFRepository: BoundaryML/baml
Length of output: 609
🏁 Script executed:
cd baml_language && grep -A 30 "def test_root_companions_attached" rig_tests/crates/python_companion_functions/customizable/test_main.pyRepository: BoundaryML/baml
Length of output: 873
Fix companion stub generation in .pyi to emit attribute declarations on parent functions.
The runtime code correctly assigns companions as parent function attributes (Parent.companion = ...), but the generated .pyi stubs at lines 1073–1079 emit bare function stubs (def companion(...) -> ret: ...). This causes type checkers and IDEs to miss the Parent.companion attribute, and duplicate companion names across different functions collide in the stub file.
Emit attribute declarations that properly reflect the runtime structure, for example:
ExtractResume.build_request: Callable[..., str]ExtractResume.parse: Callable[[str], Resume]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@baml_language/crates/baml_codegen_python/src/objects.rs` around lines 1070 -
1080, The current loop in the group.functions block is emitting bare function
stubs for companions (see fn_.companions, companion.name,
companion.render_args_str(Namespace::Other),
companion.return_type.render(Namespace::Other)), but companions are runtime
attributes on the parent function; change the generation in print_module_fn_pyi
context to emit attribute declarations on the parent function instead of
standalone defs — i.e. produce lines like "{parent_name}.{companion_name}:
Callable[{arg_types}, {ret_type}]" (import typing.Callable in the pyi output if
not present), using companion.render_args_str to build the Callable parameter
list and companion.return_type.render for the return type, so IDEs and
type-checkers see Parent.companion attributes and avoid name collisions.
| // Render sub-namespace groups. | ||
| for group in &sub_groups { | ||
| let depth = group.path.len(); // depth within baml_sync (1 = foo/, 2 = foo/bar/, etc.) | ||
| let sync_content = get_baml_sync_init_py(group, false, depth, &[]); | ||
| let async_content = get_baml_async_init_py(group, false, depth, &[]); | ||
|
|
||
| let mut sync_path = std::path::PathBuf::from("baml_sync"); | ||
| let mut async_path = std::path::PathBuf::from("baml_async"); | ||
| for seg in &group.path { | ||
| sync_path = sync_path.join(seg); | ||
| async_path = async_path.join(seg); | ||
| } | ||
| out.insert(sync_path.join("__init__.py"), sync_content); | ||
| out.insert(async_path.join("__init__.py"), async_content); | ||
|
|
||
| // Write intermediate empty __init__.py files for prefix paths. | ||
| for prefix_len in 1..group.path.len() { | ||
| let prefix = &group.path[..prefix_len]; | ||
| let mut sync_prefix_path = std::path::PathBuf::from("baml_sync"); | ||
| let mut async_prefix_path = std::path::PathBuf::from("baml_async"); | ||
| for seg in prefix { | ||
| sync_prefix_path = sync_prefix_path.join(seg); | ||
| async_prefix_path = async_prefix_path.join(seg); | ||
| } | ||
| // Only write if not already covered by a sub_group. | ||
| let prefix_vec: Vec<String> = prefix.to_vec(); | ||
| if !sub_groups.iter().any(|g| g.path == prefix_vec) { | ||
| out.entry(sync_prefix_path.join("__init__.py")) | ||
| .or_insert_with(String::new); | ||
| out.entry(async_prefix_path.join("__init__.py")) | ||
| .or_insert_with(String::new); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the file structure and the specific code section
cd /tmp && find . -type f -name "objects.rs" 2>/dev/null | head -5Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Search for the objects.rs file
find . -type f -name "objects.rs" 2>/dev/nullRepository: BoundaryML/baml
Length of output: 350
🏁 Script executed:
# Get the file size first
wc -l baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 119
🏁 Script executed:
sed -n '1136,1167p' baml_language/crates/baml_codegen_python/src/objects.rs | cat -nRepository: BoundaryML/baml
Length of output: 2060
🏁 Script executed:
sed -n '1100,1200p' baml_language/crates/baml_codegen_python/src/objects.rs | cat -nRepository: BoundaryML/baml
Length of output: 5204
🏁 Script executed:
# Search for type package related logic
rg -n "type.*package|type.*import" baml_language/crates/baml_codegen_python/src/objects.rs -A 3 -B 3 | head -60Repository: BoundaryML/baml
Length of output: 421
🏁 Script executed:
# Look for patterns related to child imports or namespaces
rg -n "sub.*namespace|child.*import|parent.*package" baml_language/crates/baml_codegen_python/src/objects.rs -A 2 -B 2 | head -100Repository: BoundaryML/baml
Length of output: 2391
🏁 Script executed:
# Get broader context - search for functions handling init.py files
rg -n "__init__" baml_language/crates/baml_codegen_python/src/objects.rs -B 5 -A 5Repository: BoundaryML/baml
Length of output: 12387
🏁 Script executed:
sed -n '1232,1280p' baml_language/crates/baml_codegen_python/src/objects.rs | cat -nRepository: BoundaryML/baml
Length of output: 1754
🏁 Script executed:
sed -n '1218,1260p' baml_language/crates/baml_codegen_python/src/objects.rs | cat -nRepository: BoundaryML/baml
Length of output: 2267
🏁 Script executed:
sed -n '1137,1170p' baml_language/crates/baml_codegen_python/src/objects.rs | cat -nRepository: BoundaryML/baml
Length of output: 2064
🏁 Script executed:
rg -n "struct FunctionGroup" baml_language/crates/baml_codegen_python/src/ -A 5Repository: BoundaryML/baml
Length of output: 610
🏁 Script executed:
sed -n '1200,1270p' baml_language/crates/baml_codegen_python/src/objects.rs | cat -nRepository: BoundaryML/baml
Length of output: 3298
Intermediate function package directories need child imports for proper attribute access.
Currently, intermediate function package __init__.py files (e.g., baml_sync/foo/ when foo has no direct functions) are written as empty. This prevents proper attribute access like baml_sync.foo.bar when bar is a child package.
The type package tree (emit_type_tree, lines 1233-1264) correctly generates from . import {child} imports for intermediate directories. Apply the same pattern to function packages: when creating intermediate __init__.py files (lines 1163-1166), collect immediate child packages and generate child imports instead of empty files.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@baml_language/crates/baml_codegen_python/src/objects.rs` around lines 1136 -
1167, Intermediate package __init__.py files are being created empty for
function packages (in the loop over sub_groups that builds
sync_prefix_path/async_prefix_path), which breaks attribute access to child
packages; fix by collecting immediate child package names for each prefix (find
g in sub_groups where g.path starts with prefix and g.path.len() == prefix_len +
1), dedupe the child segment names, build an __init__ content string with "from
. import {child}" lines for each child, and insert that string into
out.entry(sync_prefix_path.join("__init__.py"))/.or_insert and similarly for
async_prefix_path so intermediate function packages mirror the child-import
behavior used by emit_type_tree.
| for group in &sub_groups { | ||
| // Collect all prefix paths for intermediates. | ||
| for prefix_len in 1..group.fs_path.len() { | ||
| intermediate_dirs.insert(group.fs_path[..prefix_len].to_vec()); | ||
| } | ||
|
|
||
| // Write the actual __init__.py for this group. | ||
| let content = get_types_namespace_init_py(group); | ||
| let mut file_path = std::path::PathBuf::from(root_prefix); | ||
| for seg in &group.fs_path { | ||
| file_path = file_path.join(seg); | ||
| } | ||
| file_path = file_path.join("__init__.py"); | ||
| out.insert(file_path, content); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find the file and examine it
find . -name "objects.rs" -path "*/baml_codegen_python/*"Repository: BoundaryML/baml
Length of output: 116
🏁 Script executed:
# Read the specific lines mentioned in the review
sed -n '1200,1230p' baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 1364
🏁 Script executed:
# Now let's find the get_types_namespace_init_py function
rg -n "fn get_types_namespace_init_py" baml_language/crates/baml_codegen_python/src/objects.rs -A 20Repository: BoundaryML/baml
Length of output: 691
🏁 Script executed:
# Let's also check what the group structure contains
rg -n "struct.*[Gg]roup" baml_language/crates/baml_codegen_python/src/ --type rustRepository: BoundaryML/baml
Length of output: 247
🏁 Script executed:
# Let's examine the full context around the lines in question - check what precedes and follows
sed -n '1180,1240p' baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 2744
🏁 Script executed:
# See the rest of the intermediate directory handling code
sed -n '1235,1280p' baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 1298
🏁 Script executed:
# Check the get_types_root_init_py function to see if it handles imports correctly
rg -n "fn get_types_root_init_py" baml_language/crates/baml_codegen_python/src/objects.rs -A 40Repository: BoundaryML/baml
Length of output: 1784
🏁 Script executed:
# Let's see the full NamespaceGroup struct
sed -n '157,200p' baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 1800
🏁 Script executed:
# Run cargo test for the rust code to check if tests pass
cd baml_language && cargo test --lib --package baml_codegen_python 2>&1 | head -100Repository: BoundaryML/baml
Length of output: 145
🏁 Script executed:
# Search for tests related to namespace or __init__
rg -n "test.*init\|test.*namespace" baml_language/crates/baml_codegen_python/src/objects.rs -iRepository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Look for test files in the codegen_python crate
find baml_language/crates/baml_codegen_python -name "*.rs" -type f | grep -i testRepository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Search for test or example files that might show namespace behavior
find baml_language -type f -name "*.py" 2>/dev/null | head -20Repository: BoundaryML/baml
Length of output: 1364
🏁 Script executed:
# Check if there are examples or fixtures that show the expected Python output
find . -type f -name "*init*" -path "*test*" -o -name "*test*init*" 2>/dev/null | head -20Repository: BoundaryML/baml
Length of output: 1763
🏁 Script executed:
# Look for where group children might be determined or tracked
rg -n "children\|child" baml_language/crates/baml_codegen_python/src/objects.rs | head -30Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Look at the complete intermediate directory handling code to see what happens with children
sed -n '1220,1275p' baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 1950
🏁 Script executed:
# Search for where get_types_namespace_init_py is called to understand the pattern
rg -n "get_types_namespace_init_py" baml_language/crates/baml_codegen_python/src/objects.rs -B 5 -A 5Repository: BoundaryML/baml
Length of output: 1076
🏁 Script executed:
# Let's look at a broader section to understand the complete flow
sed -n '1190,1280p' baml_language/crates/baml_codegen_python/src/objects.rs | cat -nRepository: BoundaryML/baml
Length of output: 3788
🏁 Script executed:
# Let me trace through a concrete scenario to confirm the issue
# Check if there's any way child imports could be added elsewhere
rg -n "from \. import" baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 821
🏁 Script executed:
# Check what get_types_namespace_init_py's signature looks like and if there have been recent changes
git -C baml_language log --oneline -20 -- crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 82
🏁 Script executed:
# Verify by checking get_types_root_init_py which correctly handles top-level imports
sed -n '775,830p' baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 2087
Non-root parent packages must import their immediate children.
When both baml_types/foo and baml_types/foo/bar exist as separate groups, foo/__init__.py is generated by get_types_namespace_init_py(group) and contains only objects from the foo group. It does not emit from . import bar, which breaks attribute access like baml_types.foo.bar.
Intermediate import paths are only generated for prefix directories that lack their own group (line 1235 skips any dir in sub_paths). Modify get_types_namespace_init_py to accept the list of immediate child group names and emit from . import child statements before object definitions, mirroring the pattern used in get_types_root_init_py.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@baml_language/crates/baml_codegen_python/src/objects.rs` around lines 1202 -
1215, The namespace __init__ generation misses imports for immediate child
groups; update get_types_namespace_init_py to accept the immediate child names
(for groups in sub_groups whose group.fs_path has this group as prefix) and emit
"from . import <child>" lines before the object definitions, similar to the
pattern in get_types_root_init_py; locate where get_types_namespace_init_py is
called in the loop over sub_groups (uses group.fs_path and intermediate_dirs)
and pass the computed immediate child list so that non-root parent packages
(e.g., group with fs_path ["foo"] when ["foo","bar"] exists) will include from .
import bar in their generated content.
|
|
||
| [lib] | ||
| name = "baml_py" | ||
| name = "baml" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -nP '#\[pymodule\]' baml_language/crates/bridge_python/src
rg -nP '\bbaml_py\b' baml_language/crates/bridge_python/srcRepository: BoundaryML/baml
Length of output: 671
🏁 Script executed:
# Check the pymodule declaration
sed -n '25,35p' baml_language/crates/bridge_python/src/lib.rs
# Check the error definitions
sed -n '5,20p' baml_language/crates/bridge_python/src/errors.rs
# Check the comment in collector.rs
sed -n '10,15p' baml_language/crates/bridge_python/src/types/collector.rsRepository: BoundaryML/baml
Length of output: 1161
Fix stale baml_py references in exception creation and documentation.
The module rename in Cargo.toml is correct, but the following references still use the old name and must be updated:
baml_language/crates/bridge_python/src/errors.rs(lines 9–12): Thecreate_exception!macro calls must change fromcreate_exception!(baml_py, ...)tocreate_exception!(baml, ...)to match the renamed module.baml_language/crates/bridge_python/src/types/collector.rs(line 13): Update the doc comment fromfrom baml_py import Collectortofrom baml import Collector.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@baml_language/crates/bridge_python/Cargo.toml` at line 9, Update stale module
name references after renaming to "baml": in
baml_language/crates/bridge_python/src/errors.rs replace any
create_exception!(baml_py, ...) invocations with create_exception!(baml, ...) so
the macro targets the renamed crate, and in
baml_language/crates/bridge_python/src/types/collector.rs change the doc comment
import example from "from baml_py import Collector" to "from baml import
Collector".
| @@ -1,5 +1,5 @@ | |||
| [project] | |||
| name = "baml-py" | |||
| name = "baml" | |||
There was a problem hiding this comment.
Distribution rename baml-py → baml.
Note this squats the very short PyPI name baml; if publishing to PyPI is ever intended, confirm the name is owned/reserved by Boundary. Otherwise no functional concern.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@baml_language/crates/bridge_python/pyproject.toml` at line 2, The
pyproject.toml's package name was changed from "baml-py" to "baml" (the name
field in the bridge_python crate); either confirm that Boundary owns/reserved
the short PyPI name "baml" before keeping this change, or revert the name field
back to "baml-py" and update any packaging/metadata references in the
bridge_python crate (pyproject.toml name and any internal docs/release scripts)
to use the original distribution name to avoid squatting an unowned PyPI name.
| self.ctx: contextvars.ContextVar[ | ||
| typing.Dict[int, HostSpanManager] | ||
| ] = contextvars.ContextVar("baml_ctx", default={}) | ||
|
|
||
| def __mgr(self) -> HostSpanManager: | ||
| ctx = self.ctx.get() | ||
| thread_id = current_thread_id() | ||
| if thread_id not in ctx: | ||
| ctx[thread_id] = HostSpanManager() | ||
| return ctx[thread_id] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n baml_language/crates/bridge_python/python_src/baml/ctx_manager.pyRepository: BoundaryML/baml
Length of output: 7950
🏁 Script executed:
python3 << 'EOF'
import contextvars
import sys
# Test 1: Verify mutable default sharing
print("Test 1: Mutable default dict sharing")
ctx_var = contextvars.ContextVar("test", default={})
# Simulate two independent contexts
ctx1_dict = ctx_var.get()
ctx1_dict["key1"] = "value1"
print(f"After modifying in context 1: {ctx_var.get()}")
# In a new asyncio task (simulated as new context snapshot),
# the default would still be shared if we don't set() explicitly
ctx2_dict = ctx_var.get()
print(f"In context 2 without set(): {ctx2_dict}")
print(f"Are they the same object? {ctx1_dict is ctx2_dict}")
print("\n" + "="*50)
print("Test 2: Proper contextvars usage with token")
ctx_var2 = contextvars.ContextVar("test2")
ctx_var2.set({"a": 1})
token = ctx_var2.set({"b": 2})
print(f"After set with token: {ctx_var2.get()}")
ctx_var2.reset(token)
print(f"After reset(token): {ctx_var2.get()}")
EOFRepository: BoundaryML/baml
Length of output: 378
🏁 Script executed:
python3 << 'EOF'
# Scenario: Multiple threads' managers in context, then replaced with only one thread
print("Scenario: Losing other thread managers")
print()
print("Current behavior (lines 137, 150):")
print(" ctx.set({current_thread_id(): clone}) # Replaces entire dict!")
print(" → Loses any managers for other threads")
print()
print("If context had: {thread_1_id: mgr1, thread_2_id: mgr2}")
print("After line 137: {current_thread_id(): clone} ← Only current thread!")
print()
print("This breaks the stated architecture which wants:")
print(" Dict[thread_id, HostSpanManager] for thread isolation across all active threads")
EOFRepository: BoundaryML/baml
Length of output: 496
🏁 Script executed:
grep -n "ThreadPoolExecutor\|threading\|thread" baml_language/crates/bridge_python/python_src/baml/ctx_manager.pyRepository: BoundaryML/baml
Length of output: 1177
Fix mutable default and dict mutation in contextvars usage.
Line 71 uses a mutable {} default that gets shared across contexts that haven't explicitly called .set(). Line 77 then mutates this shared dict in place. Additionally, lines 137 and 150 replace the entire context dict with only the current thread's manager, losing any managers for other threads—breaking the intended architecture where Dict[thread_id, HostSpanManager] provides thread isolation.
Use .get({}) instead of a mutable default, copy the dict before mutation, and use contextvars tokens to properly restore previous context state:
Proposed fix
self.ctx: contextvars.ContextVar[
typing.Dict[int, HostSpanManager]
- ] = contextvars.ContextVar("baml_ctx", default={})
+ ] = contextvars.ContextVar("baml_ctx")
def __mgr(self) -> HostSpanManager:
- ctx = self.ctx.get()
+ ctx = self.ctx.get({})
thread_id = current_thread_id()
if thread_id not in ctx:
+ ctx = dict(ctx)
ctx[thread_id] = HostSpanManager()
+ self.ctx.set(ctx)
return ctx[thread_id] mgr = self.__mgr()
clone = mgr.deep_clone()
- self.ctx.set({current_thread_id(): clone})
+ parent_ctx = self.ctx.get({})
+ child_ctx = dict(parent_ctx)
+ child_ctx[current_thread_id()] = clone
+ token = self.ctx.set(child_ctx)
clone.enter(func_name, params)
try:
response = await func(*args, **kwargs)
clone.exit_ok()
return response
except BaseException as e:
clone.exit_error(str(e))
raise
finally:
- self.ctx.set({current_thread_id(): mgr})
+ self.ctx.reset(token)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@baml_language/crates/bridge_python/python_src/baml/ctx_manager.py` around
lines 69 - 78, The ContextVar `self.ctx` is using a shared mutable default and
is being mutated in-place in __mgr, and elsewhere code replaces the whole dict
with only the current thread's manager (losing others); change to call
self.ctx.get({}) to obtain a fresh default, copy the returned dict (e.g.,
new_ctx = dict(ctx)) before any mutation in __mgr, insert/update the current
thread key via the copy, then call self.ctx.set(new_ctx) (or capture and use the
contextvars.Token from self.ctx.set to restore prior state where needed). Also
update code paths that currently replace the context dict (the places referenced
around lines 137 and 150) to merge or copy-and-set instead of assigning a
single-entry dict so other thread managers are preserved, and use the ContextVar
token when you need to restore previous context state.
| #[cfg(windows)] | ||
| std::os::windows::fs::symlink_file(&src, &dst).unwrap_or_else(|_| { | ||
| panic!( | ||
| "Failed to symlink {} from {}", | ||
| file_name.to_string_lossy(), | ||
| src.display() | ||
| ) | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n baml_language/rig_tests/crates/python_packages_and_namespaces/build.rsRepository: BoundaryML/baml
Length of output: 7408
🏁 Script executed:
# Check for CI configurations that might indicate Windows testing
find . -name "*.yml" -o -name "*.yaml" | grep -E "(workflow|github|ci)" | head -20Repository: BoundaryML/baml
Length of output: 934
🏁 Script executed:
# Look for any documentation about Windows support
find . -name "README*" -o -name "CONTRIBUTING*" | head -10Repository: BoundaryML/baml
Length of output: 339
🏁 Script executed:
# Check if there are other Windows-specific build configurations in the codebase
rg "std::os::windows" --type rs -B 2 -A 5Repository: BoundaryML/baml
Length of output: 85
🏁 Script executed:
# Check for Windows runners in CI workflows
rg "windows" .github/workflows/ -iRepository: BoundaryML/baml
Length of output: 9626
🏁 Script executed:
# Look at CONTRIBUTING.md for Windows requirements
head -100 CONTRIBUTING.mdRepository: BoundaryML/baml
Length of output: 4173
🏁 Script executed:
# Check README-DEV.md for Windows setup info
head -150 README-DEV.mdRepository: BoundaryML/baml
Length of output: 3494
🏁 Script executed:
# Check if the customizable directory exists in this crate
find baml_language/rig_tests/crates/python_packages_and_namespaces -type d -name "customizable"Repository: BoundaryML/baml
Length of output: 133
🏁 Script executed:
# Check if there are any recent issues or discussions about Windows symlink failures
rg "symlink" .github/ -i -A 2 -B 2Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check the actual cargo-tests.reusable.yaml to see what Windows test runs
sed -n '/cargo-test-windows:/,/^ [a-z]/p' .github/workflows/cargo-tests.reusable.yaml | head -50Repository: BoundaryML/baml
Length of output: 1313
🏁 Script executed:
# Check if the customizable directory has any files in it
ls -la baml_language/rig_tests/crates/python_packages_and_namespaces/customizable/Repository: BoundaryML/baml
Length of output: 228
🏁 Script executed:
# Check the Rust version and if there are any known symlink issues in recent Rust or build scripts
rg "symlink_file" --type rs -B 3 -A 3Repository: BoundaryML/baml
Length of output: 85
🏁 Script executed:
# Verify fs::copy is already imported/available
grep -n "^use std" baml_language/rig_tests/crates/python_packages_and_namespaces/build.rsRepository: BoundaryML/baml
Length of output: 95
Windows symlink path will panic without Developer Mode / admin.
std::os::windows::fs::symlink_file requires either administrator privileges or Developer Mode enabled — on a default Windows install (including dev machines without elevated setup), this call fails with ERROR_PRIVILEGE_NOT_HELD and the unwrap_or_else panics the build. Since this is a test-fixture generator, falling back to fs::copy on symlink failure keeps the rig portable. Windows CI succeeds because the runner has elevated privileges, but local dev builds on standard Windows will fail.
Suggested fallback:
Implement fallback to fs::copy
#[cfg(windows)]
- std::os::windows::fs::symlink_file(&src, &dst).unwrap_or_else(|_| {
- panic!(
- "Failed to symlink {} from {}",
- file_name.to_string_lossy(),
- src.display()
- )
- });
+ {
+ if std::os::windows::fs::symlink_file(&src, &dst).is_err() {
+ // Developer Mode / admin not available — copy instead.
+ fs::copy(&src, &dst).unwrap_or_else(|e| {
+ panic!(
+ "Failed to copy {} from {}: {}",
+ file_name.to_string_lossy(),
+ src.display(),
+ e
+ )
+ });
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| #[cfg(windows)] | |
| std::os::windows::fs::symlink_file(&src, &dst).unwrap_or_else(|_| { | |
| panic!( | |
| "Failed to symlink {} from {}", | |
| file_name.to_string_lossy(), | |
| src.display() | |
| ) | |
| }); | |
| #[cfg(windows)] | |
| { | |
| if std::os::windows::fs::symlink_file(&src, &dst).is_err() { | |
| // Developer Mode / admin not available — copy instead. | |
| fs::copy(&src, &dst).unwrap_or_else(|e| { | |
| panic!( | |
| "Failed to copy {} from {}: {}", | |
| file_name.to_string_lossy(), | |
| src.display(), | |
| e | |
| ) | |
| }); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@baml_language/rig_tests/crates/python_packages_and_namespaces/build.rs`
around lines 60 - 67, The Windows branch currently calls
std::os::windows::fs::symlink_file(&src, &dst).unwrap_or_else(...) which panics
on ERROR_PRIVILEGE_NOT_HELD; change this to attempt symlink_file and on any
error fall back to std::fs::copy(&src, &dst) (preserving file_name/src/dst
variables), and surface a non-fatal warning (or log) instead of panicking;
implement this in the same scope where symlink_file is called (replace
unwrap_or_else with a match or if let Err(e) => { try
fs::copy(...).map_err(|copy_err| log both errors) } ) so the test-fixture
generator remains portable on non-elevated Windows.
| println!("cargo:rerun-if-changed=build.rs"); | ||
|
|
||
| // Watch all files in customizable/ folder | ||
| if customizable_dir.exists() { | ||
| for entry in fs::read_dir(&customizable_dir).unwrap() { | ||
| let entry = entry.unwrap(); | ||
| let path = entry.path(); | ||
| if path.is_file() { | ||
| println!("cargo:rerun-if-changed={}", path.display()); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Rerun-if-changed won't detect newly added files in customizable/.
Cargo's default "watch everything" behavior is disabled as soon as any cargo:rerun-if-changed directive is emitted. This loop only watches files that exist at the time the build script runs, so adding a new file to customizable/ (or deleting one) won't retrigger the build. Watching the directory itself fixes this — Cargo treats directory watches as "rerun if any entry is added/removed/modified".
🔧 Proposed fix
println!("cargo:rerun-if-changed=build.rs");
- // Watch all files in customizable/ folder
- if customizable_dir.exists() {
- for entry in fs::read_dir(&customizable_dir).unwrap() {
- let entry = entry.unwrap();
- let path = entry.path();
- if path.is_file() {
- println!("cargo:rerun-if-changed={}", path.display());
- }
- }
- }
+ // Watch the customizable/ directory itself so adding/removing files triggers a rerun,
+ // plus each existing file so edits are detected.
+ println!("cargo:rerun-if-changed={}", customizable_dir.display());
+ if customizable_dir.exists() {
+ for entry in fs::read_dir(&customizable_dir).unwrap() {
+ let entry = entry.unwrap();
+ let path = entry.path();
+ if path.is_file() {
+ println!("cargo:rerun-if-changed={}", path.display());
+ }
+ }
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@baml_language/rig_tests/crates/python_packages_and_namespaces/build.rs`
around lines 180 - 191, The rerun-if-changed loop only emits file watches for
existing files so new or deleted files in customizable_dir won't retrigger
builds; update the build script (build.rs) to also emit a watch for the
directory itself (use customizable_dir) in addition to per-file prints so Cargo
will rerun the build when entries are added/removed/modified; keep the existing
per-file loop but add a println that watches customizable_dir.display() (and
ensure customizable_dir exists before printing).
- Remove checked_value and streaming_state_value from BamlOutboundValue oneof - Delete BamlValueChecked, BamlValueStreamingState messages and BamlStreamState enum - Remove decoder branches in baml/proto.py and baml_py/proto.py - Regenerate all proto bindings (Rust, Python, Node.js, Go) - Update 09e documentation to remove deprecated table rows Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
9c40407 to
d9c15f2
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (4)
baml_language/crates/baml_codegen_python/src/objects.rs (4)
1209-1223:⚠️ Potential issue | 🟠 MajorNon-root parent type groups still don't import their immediate children.
get_types_namespace_init_py(group)(lines 756–772) renders only the objects that live ingroup.fs_path; it never emitsfrom . import <child>for sub-groups. Combined with the intermediate-directory branch at 1230–1271, the result is: when a directory has both its own group and child groups (e.g.,types/foowith objects +types/foo/bar),types/foo/__init__.pywill not exposebaras an attribute, breakingbaml_types.foo.baraccess. Either pass the immediate-child list intoget_types_namespace_init_pyand emit the imports there, or special-case the loop at 1209–1223 to merge child imports with the rendered objects.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/objects.rs` around lines 1209 - 1223, The generated __init__.py for non-root groups (created in the for loop over sub_groups) doesn't import immediate child sub-packages, so parent packages with their own objects won't expose children (breaking baml_types.foo.bar); update the code to collect immediate children for each group and ensure their imports are emitted into the parent's init content: either modify get_types_namespace_init_py(group, children: &[GroupName]) to accept a list of immediate child names and emit "from . import <child>" lines, or in the loop around get_types_namespace_init_py (the block using sub_groups, intermediate_dirs, out, and group.fs_path) merge the generated content with explicit "from . import <child>" lines for every group whose fs_path is a prefix of another group's fs_path; keep using group.fs_path to identify parent/child relationships and write the final combined content into out for the parent package.
1158-1175:⚠️ Potential issue | 🟠 MajorIntermediate function package
__init__.pyfiles are still emitted empty.When
baml_sync/foo/baris a group butbaml_sync/foois not, this loop createsbaml_sync/foo/__init__.pyasString::new(), soimport baml_sync.foo; baml_sync.foo.barwill not resolve to anything — Python won't auto-import the child subpackage. The type-tree path (emit_type_tree, lines 1242–1268) already does the right thing by gathering immediate children and emittingfrom . import {child}lines; mirror that here for the function tree.♻️ Sketch of fix
- if !sub_groups.iter().any(|g| g.path == prefix_vec) { - out.entry(sync_prefix_path.join("__init__.py")) - .or_insert_with(String::new); - out.entry(async_prefix_path.join("__init__.py")) - .or_insert_with(String::new); - } + if !sub_groups.iter().any(|g| g.path == prefix_vec) { + let children: std::collections::BTreeSet<String> = sub_groups + .iter() + .filter(|g| g.path.starts_with(prefix)) + .filter_map(|g| g.path.get(prefix.len()).cloned()) + .collect(); + let body = children + .iter() + .map(|c| format!("from . import {c}")) + .collect::<Vec<_>>() + .join("\n"); + out.entry(sync_prefix_path.join("__init__.py")) + .or_insert_with(|| body.clone()); + out.entry(async_prefix_path.join("__init__.py")) + .or_insert_with(|| body); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/objects.rs` around lines 1158 - 1175, The intermediate package __init__.py entries are being created empty (String::new()), which breaks importing child subpackages; instead, for each prefix (in the loop using group.path and prefix_len) gather the immediate child segment names (i.e., any sub_group or group whose path starts with this prefix and has length prefix_len+1), build a small file content like "from . import child1\nfrom . import child2\n" (mirroring emit_type_tree's behavior), and insert that string into out.entry(...).or_insert_with so the __init__.py will auto-import its direct children rather than being empty.
802-828:⚠️ Potential issue | 🟠 MajorGenerated
bamlsubpackage can still shadow runtimebamlreferences.The header logic still emits the
import bamlruntime stub conditionally onhas_baml_subpackage, and ontrueit omits the stub entirely. Any root-level type rendered asbaml.Options/baml.Image/ etc. (Ty::BamlOptionsandTy::Media(_)) will then resolve to the generated subpackage andAttributeErrorat attribute access.The robust fix is to import the runtime under a distinct alias (e.g.
import baml as _baml_rt) and have allTyrendering of runtime symbols emit_baml_rt.*instead ofbaml.*. The user-visiblebamlname then stays free for the generated subpackage.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/objects.rs` around lines 802 - 828, The generated header must import the runtime under an alias when a generated subpackage named baml exists: change the branch that checks has_baml_subpackage to emit e.g. "import baml as _baml_rt" (or similar alias) instead of omitting the runtime import, and keep type_file_header(ns) for the normal case; then update the type rendering code so any references to runtime types (e.g. Ty::BamlOptions and Ty::Media(_)) emit the aliased module name (_baml_rt.<Type>) rather than "baml.<Type>"—locate the Ty -> string formatter/renderer and replace uses of the literal "baml" with the chosen alias when has_baml_subpackage is true so user-visible "baml" remains the generated package while runtime symbols use the alias.
1077-1090:⚠️ Potential issue | 🟠 Major
.pyicompanion stubs are still bare module functions, notParent.companionattributes.At runtime, companions are attached as attributes on the parent function (
{fn_name}.{cname} = {private_name}, lines 968–973 / 1053–1058). The stubs at lines 1080–1086, however, emit free-standingdef {cname}(...) -> ...: ...declarations. Two consequences:
- Type checkers / IDEs do not see
ExtractResume.build_request— completion andreveal_typewill fail on the attribute form.- Companions with the same name across different parents (
build_requestfrom two different functions in the same group) collide as duplicate top-level defs in the stub.Emit attribute-typed declarations instead, e.g.:
ExtractResume.build_request: typing.Callable[..., str] ExtractResume.parse: typing.Callable[[str], Resume]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/objects.rs` around lines 1077 - 1090, The companion stubs emitted in the loop over group.functions (see fn_.companions usage in objects.rs) are currently generated as bare top-level "def" declarations; change this to emit attribute-typed declarations on the parent function symbol (fn_.name) so companions appear as Parent.companion attributes to type checkers. Concretely, replace the free-standing def emission that uses companion.render_args_str(...) and companion.return_type.render(...) with a typed attribute declaration like "ParentName.CompanionName: typing.Callable[...]" (use the companion's rendered parameter types and return type to build the Callable) and ensure typing is imported/available in the pyi output; this prevents name collisions and surfaces members as attributes on the parent function.
🧹 Nitpick comments (12)
baml_language/crates/baml_codegen_python/src/objects.rs (4)
526-528:#[allow(unreachable_pub)]on therendermod is a code smell — narrow it.Slapping
unreachable_pubon the whole module silences any future unintendedpubleak. Prefer making the genuinely-public helperspub(crate)(orpub(super)) and keeping the lint active. This will also catch the#[allow(dead_code)]markers onNamespaceGroup::public_path/NamespaceGroup::layout(lines 170, 177) once they actually start being consumed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/objects.rs` around lines 526 - 528, The module-level #[allow(unreachable_pub)] on mod render is too broad; remove that attribute and instead make only the items that need wider visibility explicitly narrower-public (e.g., change their visibility to pub(crate) or pub(super)). Locate the render module and update the visibility of the helper symbols that must be accessible (and revoke the module-level allow), and also adjust NamespaceGroup::public_path and NamespaceGroup::layout declarations (currently annotated with #[allow(dead_code]) to have the correct narrower pub(crate)/pub(super) visibility so the unreachable_pub lint stays enabled and will catch any unintended public leaks.
580-587: Six near-identical copies of thetry: import baml / except ImportError: …stub.The same ~6-line stub is duplicated across
get_types_py,get_stream_types_py,get_sync_client_py,get_async_client_py,type_file_header, andfunction_file_header. Any change (new builtin name, formatting tweak) has to be made in six places and is easy to miss. Extract a single constant orrender_fnand reuse it from each template (and from the twofn …_file_headerhelpers). This will also de-risk drift between the type-side stub list and the function-side stub list.Also applies to: 605-611, 640-646, 689-695, 747-748, 866-873
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/objects.rs` around lines 580 - 587, Multiple templates duplicate the same "try: import baml / except ImportError: ..." stub across get_types_py, get_stream_types_py, get_sync_client_py, get_async_client_py, type_file_header and function_file_header; extract that repeated text into a single reusable constant or small render function (e.g., BAML_STUB_SNIPPET or render_baml_stub()) that returns the stub string and replace the inlined copies in those functions, ensuring the canonical list of builtin names ("Image", "Audio", "Video", "Pdf", "Collector", "AbortController", "Options") is defined once and used by all templates so changes remain centralized and the two helpers (type_file_header, function_file_header) call the shared provider instead of duplicating the snippet.
945-974: Companion-call dispatch is rendered via rawformat!— fragile and hard to reuse.The sync and async branches each
format!a multi-line Python definition with embedded\nand inline{{ }}for the empty-dict literal. This is hard to read, easy to break (e.g., an extra\nslips in and indentation is off), and duplicates almost the same body twice (sync vs. async differ only inawaitandcall_function_syncvs.call_function_async). Consider promoting both torender_fn!Askama templates that take aFunction, anis_async: bool, and aparent: &Function, and have the existingprint_sync_module_fn/print_async_module_fnhelpers cover companions too.Bonus: the current code also doesn't guard against companion names that collide with Python keywords or with the parent's argument names.
Also applies to: 1032-1059
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/objects.rs` around lines 945 - 974, The companion function bodies are being built with fragile raw format! calls in the fn_.companions loop (using companion.name, private_name, companion.render_args_str, companion.render_args_dict_str, companion.render_coerce_result_str) and duplicate logic between sync/async; replace these inline multi-line format!s by extracting a reusable Askama template render_fn! that accepts a Function, is_async: bool, and parent: &Function and use the existing print_sync_module_fn / print_async_module_fn helpers to render companions too; additionally, before emitting a companion, sanitize/rename or mangle companion.name (and private_name) to avoid collisions with Python keywords or with the parent function's argument names (check parent.args) and update the attribute attachment line (fn_.name.{cname} = {private_name}) accordingly so names remain consistent.
220-247: Path extraction is duplicated three times — collapse into a helper.The three arms (
Class,Enum,TypeAlias) all do the same thing: pullpathout of eitherNamespace::Types { path }orNamespace::StreamTypes { path }and stringify it. A small helper onbaml_codegen_types::Namespace(or a free function) would cut this block by ~25 lines and make later additions (e.g., a third namespace variant) impossible to forget.♻️ Sketch
fn ns_path_strings(ns: &baml_codegen_types::Namespace) -> Vec<String> { match ns { baml_codegen_types::Namespace::Types { path } | baml_codegen_types::Namespace::StreamTypes { path } => { path.iter().map(|s| s.to_string()).collect() } } }Then each arm reduces to
ns_path_strings(&c.name.namespace), etc.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/objects.rs` around lines 220 - 247, Collapse the duplicated namespace-to-Vec<String> logic into a small helper (e.g., fn ns_path_strings(ns: &baml_codegen_types::Namespace) -> Vec<String>) that matches on baml_codegen_types::Namespace::{Types, StreamTypes} and maps path.iter().map(|s| s.to_string()).collect(), then replace the three repeated match arms that handle baml_codegen_types::Object::Class(c), Object::Enum(e), and Object::TypeAlias(ta) with calls like ns_path_strings(&c.name.namespace), ns_path_strings(&e.name.namespace), and ns_path_strings(&ta.name.namespace) respectively to eliminate the duplication.baml_language/crates/baml_codegen_python/src/objects/render/class.rs (1)
9-9:model_configplacement: minor risk of duplicate-class redefinition; otherwise fine.
model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)looks correct and is required for thebaml.Image/baml.Audio/etc. stub types injected by the generated header. No action required, but consider makingarbitrary_types_allowedopt-in (only when the class actually containsTy::Media/Ty::Callable/Ty::BuiltinUnknown) so authors don't silently lose Pydantic's strict-typing protection on plain models.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/objects/render/class.rs` at line 9, The current generated class unconditionally sets model_config = pydantic.ConfigDict(arbitrary_types_allowed=True), which can weaken Pydantic validation; change the code generator in class rendering (the place that emits model_config) to set that ConfigDict only when the class contains types that require it (detect Ty::Media, Ty::Callable, or Ty::BuiltinUnknown in the class fields), otherwise omit model_config so plain models keep strict typing; update the logic in the renderer responsible for emitting the class header (look for the function/method that emits "model_config" in class.rs) to conditionally emit model_config based on those Ty variants.baml_language/crates/baml_codegen_python/src/lib.rs (2)
22-27: Drop transient "Phase 4 / Phase 5" annotations from inline comments.These phase markers refer to PR-internal sequencing and won't mean anything to a future reader. Replace with intent-describing comments (e.g., "Emit sync/async function modules" / "Emit root package init"), so the file documents what is generated rather than which PR step it came from.
♻️ Proposed wording
- // Emit baml_sync/ and baml_async/ directory trees (Phase 4: companion functions). + // Emit baml_sync/ and baml_async/ directory trees, including companion functions. let function_groups = Function::load_function_groups(generators); objects::emit_function_tree(&mut out, function_groups); - // Emit the root baml_client/__init__.py (Phase 5). + // Emit the root baml_client/__init__.py. out.insert(PathBuf::from("__init__.py"), objects::get_client_init_py());🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/lib.rs` around lines 22 - 27, The inline comments currently use transient "Phase 4/Phase 5" annotations which are not meaningful long-term; replace those comments with intent-describing ones: change the comment above Function::load_function_groups / objects::emit_function_tree to say something like "Emit sync/async function modules (baml_sync/ and baml_async/)" and change the comment above out.insert(..., objects::get_client_init_py()) to "Emit root package __init__.py for baml_client"; locate the comments immediately preceding the Function::load_function_groups, objects::emit_function_tree, out.insert, and objects::get_client_init_py calls and update their text accordingly.
8-11:_baml_client_pathis unused; drop it or wire it through.The leading
_and the absent body usage say "intentionally unused", but the parameter is part of the public crate API. Either remove it (and update the CLI call site inbaml_cli/src/generate.rs) or actually consume it (e.g., to anchor relative paths in generated docstrings). Carrying a permanently-ignored arg invites confused callers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/lib.rs` around lines 8 - 11, The public function to_source_code currently takes an unused parameter _baml_client_path; remove this parameter from the function signature (pub fn to_source_code(generators: &baml_codegen_types::ObjectPool) -> HashMap<PathBuf,String>) and update every caller to stop passing that argument (notably the CLI call site in baml_cli/src/generate.rs) so the public API no longer carries a permanently-ignored parameter; search for to_source_code usages and adjust their calls and imports accordingly.baml_language/crates/baml_codegen_python/src/objects/render/type_alias.rs (1)
156-185: Recursive-alias unit test looks good; consider parenthesized variant too.The test correctly exercises
recursive: true+ self-reference quoting viaRenderCtx. As a follow-up, consider also adding a fixture that produces theUnion(int, List(RecursiveAlias))shape (e.g.,"int | (RecursiveAlias[])"), since the comment notes the suffix-first parser currently flips the grouping; locking in both shapes would prevent regressions when the parser is fixed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/objects/render/type_alias.rs` around lines 156 - 185, Add a second unit test variant to type_alias_recursive_self_ref that covers the parenthesized grouping so the code handles the other parse shape; create a new case using the same setup but with resolves_to: ty("int | (RecursiveAlias[])") (or similar string that forces Union(int, List(RecursiveAlias))) and then assert the rendered output still quotes the self-reference (contains "\"RecursiveAlias\"") and matches the expected TypeAliasType form for that grouping (e.g., TypeAliasType with typing.Union[int, typing.List["RecursiveAlias"]] or the equivalent ordering produced by print); reuse TypeAlias::from_codegen_types, print, and crate::ty::Namespace::Types so the new test mirrors the original one.baml_language/crates/baml_codegen_types/src/ty.rs (1)
8-25: Use the newtypes()andstream_types()helpers where paths are empty.Several call sites still construct
Namespace::Types { path: vec![] }andNamespace::StreamTypes { path: vec![] }literally instead of using the new helpers. Notable occurrences inbaml_codegen_python/src/objects/render/type_alias.rs:169,baml_codegen_tests/src/builders.rs(multiple macros and test helpers), andbaml_project/src/client_codegen.rs:704reduce readability and duplicate intent. Sweep these to use the helpers for consistency and better encapsulation.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_types/src/ty.rs` around lines 8 - 25, Replace direct constructions of empty namespaces (e.g., Namespace::Types { path: Vec::new() } and Namespace::StreamTypes { path: Vec::new() }) with the new convenience constructors Namespace::types() and Namespace::stream_types() respectively; search for literal usages of Namespace::Types { path: vec![] } and Namespace::StreamTypes { path: vec![] } (notably in places that build or render types) and update them to call Namespace::types() or Namespace::stream_types() to improve readability and encapsulation while keeping semantics unchanged.baml_language/crates/baml_codegen_python/src/objects/render/function.rs (1)
86-99: Optional: collapse*_strdelegators by exposing the underlying methods.
render_args_str,render_coerce_result_str, andrender_args_dict_strare pure pass-throughs to private methods. If the only reason for the wrappers is visibility (so non-template Rust callers can use them), making the underlyingrender_args/render_coerce_result/render_args_dictpub(super)directly removes three methods of indirection. (Skip if askama's macro expansion requires them to remainfnrather thanpub fn— easy to verify by trying the change.)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/objects/render/function.rs` around lines 86 - 99, The three delegator methods render_args_str, render_coerce_result_str, and render_args_dict_str are pure passthroughs; make the underlying private methods render_args, render_coerce_result, and render_args_dict pub(super) and remove these *_str wrapper methods to collapse indirection (verify Askama macro expansion still compiles; if macro requires private visibility, keep wrappers). Update visibility of render_args, render_coerce_result, and render_args_dict accordingly and delete the corresponding render_*_str functions.baml_language/crates/baml_codegen_python/src/ty.rs (1)
278-298: Optional: extract self-reference quoting helper.The
Class(name)andTypeAlias(name)arms have the exact same self-reference quoting block. A small helper would tighten this and ensure both branches stay in sync if the quoting policy ever changes.♻️ Proposed refactor
+ fn maybe_quote_self_ref(rendered: String, ctx: &RenderCtx) -> String { + if let Some(ref self_name) = ctx.self_ref_name { + if &rendered == self_name { + return format!("{rendered:?}"); + } + } + rendered + } + pub(crate) fn render_with_ctx(&self, ctx: &RenderCtx) -> String { match self { ... - Ty::Class(name) => { - let rendered = name.render_ctx(ctx); - if let Some(ref self_name) = ctx.self_ref_name { - if &rendered == self_name { - return format!("{rendered:?}"); - } - } - rendered - } + Ty::Class(name) => Self::maybe_quote_self_ref(name.render_ctx(ctx), ctx), Ty::Enum(name) => name.render_ctx(ctx), - Ty::TypeAlias(name) => { - let rendered = name.render_ctx(ctx); - if let Some(ref self_name) = ctx.self_ref_name { - if &rendered == self_name { - return format!("{rendered:?}"); - } - } - rendered - } + Ty::TypeAlias(name) => Self::maybe_quote_self_ref(name.render_ctx(ctx), ctx),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_codegen_python/src/ty.rs` around lines 278 - 298, Ty::Class and Ty::TypeAlias duplicate the same self-reference quoting logic; extract that into a small helper (e.g., a function like quote_self_if_needed or method on ctx) that takes the rendered name (from name.render_ctx(ctx)) and ctx.self_ref_name and returns either the quoted string or the original rendered string. Replace the duplicated blocks in the Ty::Class and Ty::TypeAlias arms to call this helper after computing rendered so both branches delegate to the single helper and stay in sync.baml_language/crates/baml_project/src/client_codegen.rs (1)
260-283: Companion-with-no-parent silently drops; consider a diagnostic.A function with a
$-suffixed name whose parent isn't a declarative LLM function (or is a generic, filtered out at line 261) gets pushed ontopending_functionsthen silently dropped at the second-pass attachment loop (line 372). The comment at 372 acknowledges this. In valid code this won't happen, but atracing::debug!(or assertion-in-test) would make orphans easier to diagnose if companion expansion ever gets out of sync with the parent filter.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@baml_language/crates/baml_project/src/client_codegen.rs` around lines 260 - 283, When encountering a `$`-suffixed companion in split_companion while iterating item_tree.functions (and after skipping generics via func.generic_params), emit a diagnostic instead of silently dropping it: when companion_suffix.is_some() but the parent is not treated as a declarative LLM parent (i.e., companion_suffix.is_some() && companion_suffix.map_or(true, |_| parent not in declarative LLM set)), call tracing::debug! (or tracing::warn! in non-test builds) including func.name and the derived parent name, and in tests assert/expect this diagnostic so orphans are visible; locate this logic around split_companion, the companion_suffix handling, and where pending_functions is used to attach companions to parents.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@baml_language/crates/baml_codegen_python/src/objects.rs`:
- Around line 1093-1177: The test-suite for the Rust crate wasn't run after the
rewrite; run cargo test --lib for the baml_codegen_python crate and fix any
failing unit tests (particularly in the objects/render/* modules) before
merging: run tests that exercise emit_function_tree and emit_type_tree and the
rendering helpers (e.g., get_baml_sync_init_py, get_baml_sync_init_pyi,
get_baml_async_init_py) and update code to satisfy assertions (fix logic around
root/sub-group partitioning, top_level_submodules collection, path prefix
__init__ insertion, or any changed APIs) until all unit tests pass.
- Around line 285-293: The wire_name is currently taken verbatim from
Function.name which omits namespace and parent context, causing runtime lookup
failures for namespaced functions and companion functions; update the code that
sets wire_name (where Function.name is used) to build the fully-qualified
runtime name by prepending the function's namespace path and, for companions
(e.g., build_request, parse), prepend the parent function's bare name (e.g.,
"ExtractResume.build_request"), ensuring it matches how names are registered in
resolved_function_names during bytecode generation so lookup_function can find
exact matches (and still allow the "user." fallback).
In `@baml_language/crates/baml_codegen_python/src/objects/render/function.rs`:
- Around line 39-63: Update the class-method Askama templates used by
print_sync_impl and print_async_impl to use the function's wire_name when
calling the runtime instead of function_.name; locate the templates in those two
render functions (the class-method implementations) and replace occurrences like
{{ function_.name }} used for runtime dispatch/call with {{ function_.wire_name
}} so companion functions use their bare wire name consistent with
print_sync_module_fn and print_async_module_fn.
In `@baml_language/crates/baml_codegen_tests/src/builders.rs`:
- Around line 513-532: The recursive detection currently uses substring matching
($ty.contains($name)/contains(name_str)) which yields false positives; change
the check to tokenize the RHS type string on non-identifier characters and test
for an exact identifier match to the alias name. In both macro arms replace the
is_recursive assignment with a tokenized check (e.g., split $ty on chars where
!is_alphanumeric && != '_' and see if any token == $name or name_str) so
TypeAlias { ..., recursive: <token-equals-check> } uses exact identifier
equality rather than contains. Ensure you apply the same logic in the
($name:literal = $ty:literal) and ($name:ident = $ty:literal) branches so
TypeAlias, name(...), and resolves_to(...) remain unchanged.
---
Duplicate comments:
In `@baml_language/crates/baml_codegen_python/src/objects.rs`:
- Around line 1209-1223: The generated __init__.py for non-root groups (created
in the for loop over sub_groups) doesn't import immediate child sub-packages, so
parent packages with their own objects won't expose children (breaking
baml_types.foo.bar); update the code to collect immediate children for each
group and ensure their imports are emitted into the parent's init content:
either modify get_types_namespace_init_py(group, children: &[GroupName]) to
accept a list of immediate child names and emit "from . import <child>" lines,
or in the loop around get_types_namespace_init_py (the block using sub_groups,
intermediate_dirs, out, and group.fs_path) merge the generated content with
explicit "from . import <child>" lines for every group whose fs_path is a prefix
of another group's fs_path; keep using group.fs_path to identify parent/child
relationships and write the final combined content into out for the parent
package.
- Around line 1158-1175: The intermediate package __init__.py entries are being
created empty (String::new()), which breaks importing child subpackages;
instead, for each prefix (in the loop using group.path and prefix_len) gather
the immediate child segment names (i.e., any sub_group or group whose path
starts with this prefix and has length prefix_len+1), build a small file content
like "from . import child1\nfrom . import child2\n" (mirroring emit_type_tree's
behavior), and insert that string into out.entry(...).or_insert_with so the
__init__.py will auto-import its direct children rather than being empty.
- Around line 802-828: The generated header must import the runtime under an
alias when a generated subpackage named baml exists: change the branch that
checks has_baml_subpackage to emit e.g. "import baml as _baml_rt" (or similar
alias) instead of omitting the runtime import, and keep type_file_header(ns) for
the normal case; then update the type rendering code so any references to
runtime types (e.g. Ty::BamlOptions and Ty::Media(_)) emit the aliased module
name (_baml_rt.<Type>) rather than "baml.<Type>"—locate the Ty -> string
formatter/renderer and replace uses of the literal "baml" with the chosen alias
when has_baml_subpackage is true so user-visible "baml" remains the generated
package while runtime symbols use the alias.
- Around line 1077-1090: The companion stubs emitted in the loop over
group.functions (see fn_.companions usage in objects.rs) are currently generated
as bare top-level "def" declarations; change this to emit attribute-typed
declarations on the parent function symbol (fn_.name) so companions appear as
Parent.companion attributes to type checkers. Concretely, replace the
free-standing def emission that uses companion.render_args_str(...) and
companion.return_type.render(...) with a typed attribute declaration like
"ParentName.CompanionName: typing.Callable[...]" (use the companion's rendered
parameter types and return type to build the Callable) and ensure typing is
imported/available in the pyi output; this prevents name collisions and surfaces
members as attributes on the parent function.
---
Nitpick comments:
In `@baml_language/crates/baml_codegen_python/src/lib.rs`:
- Around line 22-27: The inline comments currently use transient "Phase 4/Phase
5" annotations which are not meaningful long-term; replace those comments with
intent-describing ones: change the comment above Function::load_function_groups
/ objects::emit_function_tree to say something like "Emit sync/async function
modules (baml_sync/ and baml_async/)" and change the comment above
out.insert(..., objects::get_client_init_py()) to "Emit root package __init__.py
for baml_client"; locate the comments immediately preceding the
Function::load_function_groups, objects::emit_function_tree, out.insert, and
objects::get_client_init_py calls and update their text accordingly.
- Around line 8-11: The public function to_source_code currently takes an unused
parameter _baml_client_path; remove this parameter from the function signature
(pub fn to_source_code(generators: &baml_codegen_types::ObjectPool) ->
HashMap<PathBuf,String>) and update every caller to stop passing that argument
(notably the CLI call site in baml_cli/src/generate.rs) so the public API no
longer carries a permanently-ignored parameter; search for to_source_code usages
and adjust their calls and imports accordingly.
In `@baml_language/crates/baml_codegen_python/src/objects.rs`:
- Around line 526-528: The module-level #[allow(unreachable_pub)] on mod render
is too broad; remove that attribute and instead make only the items that need
wider visibility explicitly narrower-public (e.g., change their visibility to
pub(crate) or pub(super)). Locate the render module and update the visibility of
the helper symbols that must be accessible (and revoke the module-level allow),
and also adjust NamespaceGroup::public_path and NamespaceGroup::layout
declarations (currently annotated with #[allow(dead_code]) to have the correct
narrower pub(crate)/pub(super) visibility so the unreachable_pub lint stays
enabled and will catch any unintended public leaks.
- Around line 580-587: Multiple templates duplicate the same "try: import baml /
except ImportError: ..." stub across get_types_py, get_stream_types_py,
get_sync_client_py, get_async_client_py, type_file_header and
function_file_header; extract that repeated text into a single reusable constant
or small render function (e.g., BAML_STUB_SNIPPET or render_baml_stub()) that
returns the stub string and replace the inlined copies in those functions,
ensuring the canonical list of builtin names ("Image", "Audio", "Video", "Pdf",
"Collector", "AbortController", "Options") is defined once and used by all
templates so changes remain centralized and the two helpers (type_file_header,
function_file_header) call the shared provider instead of duplicating the
snippet.
- Around line 945-974: The companion function bodies are being built with
fragile raw format! calls in the fn_.companions loop (using companion.name,
private_name, companion.render_args_str, companion.render_args_dict_str,
companion.render_coerce_result_str) and duplicate logic between sync/async;
replace these inline multi-line format!s by extracting a reusable Askama
template render_fn! that accepts a Function, is_async: bool, and parent:
&Function and use the existing print_sync_module_fn / print_async_module_fn
helpers to render companions too; additionally, before emitting a companion,
sanitize/rename or mangle companion.name (and private_name) to avoid collisions
with Python keywords or with the parent function's argument names (check
parent.args) and update the attribute attachment line (fn_.name.{cname} =
{private_name}) accordingly so names remain consistent.
- Around line 220-247: Collapse the duplicated namespace-to-Vec<String> logic
into a small helper (e.g., fn ns_path_strings(ns:
&baml_codegen_types::Namespace) -> Vec<String>) that matches on
baml_codegen_types::Namespace::{Types, StreamTypes} and maps path.iter().map(|s|
s.to_string()).collect(), then replace the three repeated match arms that handle
baml_codegen_types::Object::Class(c), Object::Enum(e), and Object::TypeAlias(ta)
with calls like ns_path_strings(&c.name.namespace),
ns_path_strings(&e.name.namespace), and ns_path_strings(&ta.name.namespace)
respectively to eliminate the duplication.
In `@baml_language/crates/baml_codegen_python/src/objects/render/class.rs`:
- Line 9: The current generated class unconditionally sets model_config =
pydantic.ConfigDict(arbitrary_types_allowed=True), which can weaken Pydantic
validation; change the code generator in class rendering (the place that emits
model_config) to set that ConfigDict only when the class contains types that
require it (detect Ty::Media, Ty::Callable, or Ty::BuiltinUnknown in the class
fields), otherwise omit model_config so plain models keep strict typing; update
the logic in the renderer responsible for emitting the class header (look for
the function/method that emits "model_config" in class.rs) to conditionally emit
model_config based on those Ty variants.
In `@baml_language/crates/baml_codegen_python/src/objects/render/function.rs`:
- Around line 86-99: The three delegator methods render_args_str,
render_coerce_result_str, and render_args_dict_str are pure passthroughs; make
the underlying private methods render_args, render_coerce_result, and
render_args_dict pub(super) and remove these *_str wrapper methods to collapse
indirection (verify Askama macro expansion still compiles; if macro requires
private visibility, keep wrappers). Update visibility of render_args,
render_coerce_result, and render_args_dict accordingly and delete the
corresponding render_*_str functions.
In `@baml_language/crates/baml_codegen_python/src/objects/render/type_alias.rs`:
- Around line 156-185: Add a second unit test variant to
type_alias_recursive_self_ref that covers the parenthesized grouping so the code
handles the other parse shape; create a new case using the same setup but with
resolves_to: ty("int | (RecursiveAlias[])") (or similar string that forces
Union(int, List(RecursiveAlias))) and then assert the rendered output still
quotes the self-reference (contains "\"RecursiveAlias\"") and matches the
expected TypeAliasType form for that grouping (e.g., TypeAliasType with
typing.Union[int, typing.List["RecursiveAlias"]] or the equivalent ordering
produced by print); reuse TypeAlias::from_codegen_types, print, and
crate::ty::Namespace::Types so the new test mirrors the original one.
In `@baml_language/crates/baml_codegen_python/src/ty.rs`:
- Around line 278-298: Ty::Class and Ty::TypeAlias duplicate the same
self-reference quoting logic; extract that into a small helper (e.g., a function
like quote_self_if_needed or method on ctx) that takes the rendered name (from
name.render_ctx(ctx)) and ctx.self_ref_name and returns either the quoted string
or the original rendered string. Replace the duplicated blocks in the Ty::Class
and Ty::TypeAlias arms to call this helper after computing rendered so both
branches delegate to the single helper and stay in sync.
In `@baml_language/crates/baml_codegen_types/src/ty.rs`:
- Around line 8-25: Replace direct constructions of empty namespaces (e.g.,
Namespace::Types { path: Vec::new() } and Namespace::StreamTypes { path:
Vec::new() }) with the new convenience constructors Namespace::types() and
Namespace::stream_types() respectively; search for literal usages of
Namespace::Types { path: vec![] } and Namespace::StreamTypes { path: vec![] }
(notably in places that build or render types) and update them to call
Namespace::types() or Namespace::stream_types() to improve readability and
encapsulation while keeping semantics unchanged.
In `@baml_language/crates/baml_project/src/client_codegen.rs`:
- Around line 260-283: When encountering a `$`-suffixed companion in
split_companion while iterating item_tree.functions (and after skipping generics
via func.generic_params), emit a diagnostic instead of silently dropping it:
when companion_suffix.is_some() but the parent is not treated as a declarative
LLM parent (i.e., companion_suffix.is_some() && companion_suffix.map_or(true,
|_| parent not in declarative LLM set)), call tracing::debug! (or tracing::warn!
in non-test builds) including func.name and the derived parent name, and in
tests assert/expect this diagnostic so orphans are visible; locate this logic
around split_companion, the companion_suffix handling, and where
pending_functions is used to attach companions to parents.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: c41e0b78-dc0d-491f-a885-2d30630414b3
⛔ Files ignored due to path filters (1)
baml_language/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (17)
baml_language/crates/baml_cli/src/generate.rsbaml_language/crates/baml_codegen_python/src/_askama/config.pybaml_language/crates/baml_codegen_python/src/_askama/globals.pybaml_language/crates/baml_codegen_python/src/_askama/runtime.pybaml_language/crates/baml_codegen_python/src/lib.rsbaml_language/crates/baml_codegen_python/src/objects.rsbaml_language/crates/baml_codegen_python/src/objects/render/class.rsbaml_language/crates/baml_codegen_python/src/objects/render/function.rsbaml_language/crates/baml_codegen_python/src/objects/render/type_alias.rsbaml_language/crates/baml_codegen_python/src/ty.rsbaml_language/crates/baml_codegen_tests/Cargo.tomlbaml_language/crates/baml_codegen_tests/src/builders.rsbaml_language/crates/baml_codegen_tests/src/lib.rsbaml_language/crates/baml_codegen_types/Cargo.tomlbaml_language/crates/baml_codegen_types/src/objects.rsbaml_language/crates/baml_codegen_types/src/ty.rsbaml_language/crates/baml_project/src/client_codegen.rs
✅ Files skipped from review due to trivial changes (3)
- baml_language/crates/baml_codegen_types/Cargo.toml
- baml_language/crates/baml_codegen_tests/Cargo.toml
- baml_language/crates/baml_codegen_python/src/_askama/config.py
🚧 Files skipped from review as they are similar to previous changes (2)
- baml_language/crates/baml_codegen_python/src/_askama/globals.py
- baml_language/crates/baml_codegen_python/src/_askama/runtime.py
| /// The "wire name" used when calling the runtime — usually the same as `name`, | ||
| /// but for companion functions this is the companion's bare name. | ||
| wire_name: String, | ||
| assembed_docstring: DocString, | ||
| arguments: Vec<FunctionArgument>, | ||
| return_type: Ty, | ||
| /// Companion functions attached to this function (e.g. `build_request`, `parse`). | ||
| companions: Vec<Function>, | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# How is function_name interpreted by the runtime / call dispatcher?
rg -nP --type=rust -C5 'function_name' baml_language/crates/bridge_python/src
rg -nP --type=rust -C5 'fn call_function_sync|fn call_function_async' baml_language/crates
# Look for FQN/dot-name normalization on the runtime side.
rg -nP --type=rust -C3 '"\\.".*join|format!\("\{[^}]*\}\.\{' baml_language/crates/baml_project/src/client_codegen.rsRepository: BoundaryML/baml
Length of output: 15099
🏁 Script executed:
wc -l baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 120
🏁 Script executed:
sed -n '280,300p' baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 808
🏁 Script executed:
sed -n '470,485p' baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 453
🏁 Script executed:
sed -n '420,480p' baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 2172
🏁 Script executed:
sed -n '400,430p' baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 984
🏁 Script executed:
sed -n '350,420p' baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 2486
🏁 Script executed:
rg -n 'wire_name' baml_language/crates/baml_codegen_python/src/Repository: BoundaryML/baml
Length of output: 745
🏁 Script executed:
sed -n '40,70p' baml_language/crates/baml_codegen_python/src/objects/render/function.rsRepository: BoundaryML/baml
Length of output: 1598
🏁 Script executed:
rg -n 'call_function' baml_language/crates/bex_project/src/ -A 5 | head -100Repository: BoundaryML/baml
Length of output: 7133
🏁 Script executed:
find baml_language/crates/bex_engine -name "*.rs" | head -5Repository: BoundaryML/baml
Length of output: 322
🏁 Script executed:
rg -n 'fn call_function' baml_language/crates/bex_engine/src/ -A 10 | head -60Repository: BoundaryML/baml
Length of output: 877
🏁 Script executed:
sed -n '808,870p' baml_language/crates/bex_engine/src/lib.rsRepository: BoundaryML/baml
Length of output: 2376
🏁 Script executed:
rg -n 'fn lookup_function' baml_language/crates/bex_engine/src/ -A 15Repository: BoundaryML/baml
Length of output: 1511
🏁 Script executed:
rg -n 'resolved_function_names' baml_language/crates/bex_engine/src/lib.rs -B 2 -A 2 | head -80Repository: BoundaryML/baml
Length of output: 3932
🏁 Script executed:
rg -n 'resolved_function_names' baml_language/crates/baml_codegen -A 2 -B 2 | head -100Repository: BoundaryML/baml
Length of output: 133
🏁 Script executed:
find baml_language/crates -name "*.rs" -type f | xargs rg -l 'resolved_function_names' | head -10Repository: BoundaryML/baml
Length of output: 140
Confirm that wire_name includes necessary namespace and parent function context for runtime resolution.
wire_name is set verbatim from codegen-types Function.name (line 477) without considering namespace or parent function context. The runtime's lookup_function (in bex_engine) tries:
- Exact match on the provided name
- Fallback to "user.{name}" prefix
This suggests two problems:
-
Namespaced functions (e.g.,
fn "foo.ClassifySentiment"): IfFunction.nameis just"ClassifySentiment", the lookup will fail unless the function is registered exactly as"ClassifySentiment"or"user.ClassifySentiment". The namespace path is not automatically applied. -
Companion functions (e.g.,
build_request): Ifwire_nameis just"build_request", the lookup will fail unless the companion is registered under that exact name. The parent function name is not prepended.
Verify how functions are registered in resolved_function_names during bytecode generation. If namespaced functions and companions are registered with their fully-qualified names (e.g., "foo.ClassifySentiment", "ExtractResume.build_request"), then wire_name must be derived to include the namespace path and parent function name.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@baml_language/crates/baml_codegen_python/src/objects.rs` around lines 285 -
293, The wire_name is currently taken verbatim from Function.name which omits
namespace and parent context, causing runtime lookup failures for namespaced
functions and companion functions; update the code that sets wire_name (where
Function.name is used) to build the fully-qualified runtime name by prepending
the function's namespace path and, for companions (e.g., build_request, parse),
prepend the parent function's bare name (e.g., "ExtractResume.build_request"),
ensuring it matches how names are registered in resolved_function_names during
bytecode generation so lookup_function can find exact matches (and still allow
the "user." fallback).
| /// Emit the full baml_sync (and baml_async) function trees into `out`. | ||
| /// | ||
| /// Produces: | ||
| /// `baml_sync/__init__.py` + `__init__.pyi` | ||
| /// `baml_sync/<path>/__init__.py` (for each non-root group) | ||
| /// `baml_async/__init__.py` | ||
| /// `baml_async/<path>/__init__.py` (for each non-root group) | ||
| pub fn emit_function_tree( | ||
| out: &mut std::collections::HashMap<std::path::PathBuf, String>, | ||
| groups: Vec<FunctionGroup>, | ||
| ) { | ||
| // Separate root group (empty path) from sub-groups. | ||
| let (root_groups, sub_groups): (Vec<_>, Vec<_>) = | ||
| groups.into_iter().partition(|g| g.path.is_empty()); | ||
|
|
||
| let root_group = root_groups.into_iter().next().unwrap_or(FunctionGroup { | ||
| path: vec![], | ||
| functions: vec![], | ||
| }); | ||
|
|
||
| // Collect sorted top-level sub-namespace names. | ||
| let top_level_submodules: Vec<String> = { | ||
| let set: std::collections::BTreeSet<String> = sub_groups | ||
| .iter() | ||
| .filter_map(|g| g.path.first().cloned()) | ||
| .collect(); | ||
| set.into_iter().collect() | ||
| }; | ||
|
|
||
| // Render root sync __init__.py (sub-namespace imports are passed in, placed at top). | ||
| let sync_root = get_baml_sync_init_py(&root_group, true, 0, &top_level_submodules); | ||
| out.insert( | ||
| std::path::PathBuf::from("baml_sync").join("__init__.py"), | ||
| sync_root, | ||
| ); | ||
|
|
||
| // Render root sync __init__.pyi. | ||
| let sync_pyi = get_baml_sync_init_pyi(&root_group); | ||
| out.insert( | ||
| std::path::PathBuf::from("baml_sync").join("__init__.pyi"), | ||
| sync_pyi, | ||
| ); | ||
|
|
||
| // Render root async __init__.py. | ||
| let async_root = get_baml_async_init_py(&root_group, true, 0, &top_level_submodules); | ||
| out.insert( | ||
| std::path::PathBuf::from("baml_async").join("__init__.py"), | ||
| async_root, | ||
| ); | ||
|
|
||
| // Render sub-namespace groups. | ||
| for group in &sub_groups { | ||
| let depth = group.path.len(); // depth within baml_sync (1 = foo/, 2 = foo/bar/, etc.) | ||
| let sync_content = get_baml_sync_init_py(group, false, depth, &[]); | ||
| let async_content = get_baml_async_init_py(group, false, depth, &[]); | ||
|
|
||
| let mut sync_path = std::path::PathBuf::from("baml_sync"); | ||
| let mut async_path = std::path::PathBuf::from("baml_async"); | ||
| for seg in &group.path { | ||
| sync_path = sync_path.join(seg); | ||
| async_path = async_path.join(seg); | ||
| } | ||
| out.insert(sync_path.join("__init__.py"), sync_content); | ||
| out.insert(async_path.join("__init__.py"), async_content); | ||
|
|
||
| // Write intermediate empty __init__.py files for prefix paths. | ||
| for prefix_len in 1..group.path.len() { | ||
| let prefix = &group.path[..prefix_len]; | ||
| let mut sync_prefix_path = std::path::PathBuf::from("baml_sync"); | ||
| let mut async_prefix_path = std::path::PathBuf::from("baml_async"); | ||
| for seg in prefix { | ||
| sync_prefix_path = sync_prefix_path.join(seg); | ||
| async_prefix_path = async_prefix_path.join(seg); | ||
| } | ||
| // Only write if not already covered by a sub_group. | ||
| let prefix_vec: Vec<String> = prefix.to_vec(); | ||
| if !sub_groups.iter().any(|g| g.path == prefix_vec) { | ||
| out.entry(sync_prefix_path.join("__init__.py")) | ||
| .or_insert_with(String::new); | ||
| out.entry(async_prefix_path.join("__init__.py")) | ||
| .or_insert_with(String::new); | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm there are unit tests that actually cover emit_function_tree / emit_type_tree.
rg -nP --type=rust -C3 'emit_function_tree|emit_type_tree' baml_language/crates/baml_codegen_pythonRepository: BoundaryML/baml
Length of output: 3193
🏁 Script executed:
#!/bin/bash
# Find test files and test modules in baml_codegen_python crate
echo "=== Checking for tests/ directory ==="
find baml_language/crates/baml_codegen_python -type d -name tests
echo -e "\n=== Checking for test modules in src/ ==="
rg -n "#\[cfg\(test\)\]" baml_language/crates/baml_codegen_python/src
echo -e "\n=== Listing src files ==="
find baml_language/crates/baml_codegen_python/src -type f -name "*.rs" | head -20
echo -e "\n=== Checking Cargo.toml for test configuration ==="
cat baml_language/crates/baml_codegen_python/Cargo.toml | grep -A 10 "\[\[test\]\]" || echo "No explicit test section found"Repository: BoundaryML/baml
Length of output: 1241
🏁 Script executed:
#!/bin/bash
# Search for test modules in objects.rs
echo "=== Checking for test modules in objects.rs ==="
rg -n "#\[cfg\(test\)\]" baml_language/crates/baml_codegen_python/src/objects.rs -A 20
echo -e "\n=== Check line count of objects.rs ==="
wc -l baml_language/crates/baml_codegen_python/src/objects.rs
echo -e "\n=== Look for any #[test] attributes in objects.rs ==="
rg -n "#\[test\]" baml_language/crates/baml_codegen_python/src/objects.rsRepository: BoundaryML/baml
Length of output: 263
🏁 Script executed:
#!/bin/bash
# Look at the test module in objects/render/function.rs
echo "=== Test module in function.rs ==="
rg -n "#\[cfg\(test\)\]" baml_language/crates/baml_codegen_python/src/objects/render/function.rs -A 50 | head -70
echo -e "\n=== Check if emit_function_tree is tested anywhere ==="
rg -r "emit_function_tree" baml_language/crates/baml_codegen_python --type=rustRepository: BoundaryML/baml
Length of output: 1841
🏁 Script executed:
#!/bin/bash
# Check if emit_function_tree is actually called and tested
echo "=== Search broader for emit_function_tree usage ==="
git -C . grep -n "emit_function_tree" -- "*.rs" 2>/dev/null || rg "emit_function_tree" --type=rust -n
echo -e "\n=== Check if baml_codegen_python is tested through integration tests ==="
find baml_language/crates -name "*.rs" -type f | xargs grep -l "baml_codegen_python" | head -10
echo -e "\n=== Verify what lib.rs actually does (top of file) ==="
head -30 baml_language/crates/baml_codegen_python/src/lib.rsRepository: BoundaryML/baml
Length of output: 1648
🏁 Script executed:
#!/bin/bash
# Check if there are integration tests that use baml_codegen_python
echo "=== Check baml_cli generate.rs for codegen usage ==="
rg -n "baml_codegen_python" baml_language/crates/baml_cli/src/generate.rs -B 2 -A 5
echo -e "\n=== Look for test fixtures or integration test directories ==="
find baml_language/crates -type d -name "tests" -o -type d -name "test_fixtures"
echo -e "\n=== Check if there are any insta snapshot files for codegen_python ==="
find baml_language/crates/baml_codegen_python -name "*.snap" -o -name "*.insta"Repository: BoundaryML/baml
Length of output: 1151
🏁 Script executed:
#!/bin/bash
# Check the baml_tests crate to see if it tests codegen output
echo "=== Search baml_tests for codegen_python references ==="
rg "baml_codegen_python\|emit_function_tree\|emit_type_tree" baml_language/crates/baml_tests --type=rust
echo -e "\n=== Check baml_project for codegen usage ==="
rg -n "baml_codegen_python" baml_language/crates/baml_project/src/client_codegen.rs -B 3 -A 10 | head -40
echo -e "\n=== Look for Python file snapshot tests or comparisons ==="
find integ-tests -name "*.py" -type f | head -5
ls -la integ-tests/python-v1/baml_client/ 2>/dev/null | head -20Repository: BoundaryML/baml
Length of output: 2015
Run cargo test --lib for baml_codegen_python; verify the unit tests pass given the magnitude of this rewrite.
Unit tests exist in this crate (in the objects/render/* modules), so per coding guidelines—"Always run cargo test --lib if you changed any Rust code"—these should pass before merging. The tree emission functions themselves (emit_function_tree, emit_type_tree) are tested indirectly through the integration test pipeline via baml_cli generate, but the rendering helper tests should confirm the low-level codegen logic is sound.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@baml_language/crates/baml_codegen_python/src/objects.rs` around lines 1093 -
1177, The test-suite for the Rust crate wasn't run after the rewrite; run cargo
test --lib for the baml_codegen_python crate and fix any failing unit tests
(particularly in the objects/render/* modules) before merging: run tests that
exercise emit_function_tree and emit_type_tree and the rendering helpers (e.g.,
get_baml_sync_init_py, get_baml_sync_init_pyi, get_baml_async_init_py) and
update code to satisfy assertions (fix logic around root/sub-group partitioning,
top_level_submodules collection, path prefix __init__ insertion, or any changed
APIs) until all unit tests pass.
| baml_codegen_types::render_fn! { | ||
| /// ```askama | ||
| /// def {{function_.name}}{{ function_.render_args(*ns) }} -> {{ function_.return_type.render(*ns) }}: | ||
| /// {{ function_.assembed_docstring.as_docstring()|indent(4) }} | ||
| /// __result__ = _get_runtime().merge_options(baml_options or {}).call_function_sync( | ||
| /// function_name="{{ function_.wire_name }}", | ||
| /// args={{ function_.render_args_dict() }}, | ||
| /// ) | ||
| /// return {{ function_.render_coerce_result(*ns) }} | ||
| /// ``` | ||
| pub fn print_sync_module_fn(function_: &crate::objects::Function, ns: crate::ty::Namespace) -> String; | ||
| } | ||
|
|
||
| baml_codegen_types::render_fn! { | ||
| /// ```askama | ||
| /// async def {{function_.name}}{{ function_.render_args(*ns) }} -> {{ function_.return_type.render(*ns) }}: | ||
| /// {{ function_.assembed_docstring.as_docstring()|indent(4) }} | ||
| /// __result__ = await _get_runtime().merge_options(baml_options or {}).call_function_async( | ||
| /// function_name="{{ function_.wire_name }}", | ||
| /// args={{ function_.render_args_dict() }}, | ||
| /// ) | ||
| /// return {{ function_.render_coerce_result(*ns) }} | ||
| /// ``` | ||
| pub fn print_async_module_fn(function_: &crate::objects::Function, ns: crate::ty::Namespace) -> String; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm `wire_name` is a field on the codegen Function struct used by these templates.
fd -t f 'objects.rs' baml_language/crates/baml_codegen_python/src
echo "----- struct Function definition -----"
ast-grep --pattern $'pub struct Function {
$$$
}'
echo "----- fields named wire_name -----"
rg -nP --type=rust '\bwire_name\b' baml_language/crates/baml_codegen_python baml_language/crates/baml_codegen_types
echo "----- uses of function_.name vs function_.wire_name in templates -----"
rg -nP --type=rust '\bfunction_\.(name|wire_name)\b' baml_language/crates/baml_codegen_pythonRepository: BoundaryML/baml
Length of output: 16987
The wire_name field exists and is correctly used in the new module-level templates, but the existing class-method templates likely need to be updated for consistency.
wire_name is a String field defined at baml_language/crates/baml_codegen_python/src/objects.rs:287 with documented semantics: "used when calling the runtime — usually the same as name, but for companion functions this is the companion's bare name."
The new print_sync_module_fn and print_async_module_fn templates correctly use {{ function_.wire_name }} (lines 44, 57). However, the existing class-method templates print_sync_impl and print_async_impl use {{ function_.name }} (lines 16, 31). Since wire_name differs from name for companion functions (based on the field's documented behavior), and the struct contains a companions: Vec<Function> field, the class-method templates should also switch to {{ function_.wire_name }} to ensure runtime function dispatch uses the bare name for companions, matching the module-level behavior.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@baml_language/crates/baml_codegen_python/src/objects/render/function.rs`
around lines 39 - 63, Update the class-method Askama templates used by
print_sync_impl and print_async_impl to use the function's wire_name when
calling the runtime instead of function_.name; locate the templates in those two
render functions (the class-method implementations) and replace occurrences like
{{ function_.name }} used for runtime dispatch/call with {{ function_.wire_name
}} so companion functions use their bare wire name consistent with
print_sync_module_fn and print_async_module_fn.
| ($name:literal = $ty:literal) => {{ | ||
| // Detect recursive type alias by checking if the name appears in the type string. | ||
| let is_recursive = $ty.contains($name); | ||
| $crate::TypeAlias { | ||
| name: $crate::name($name), | ||
| resolves_to: $crate::ty($ty), | ||
| recursive: is_recursive, | ||
| } | ||
| }; | ||
| }}; | ||
| // Identifier name (for use in other macros) | ||
| ($name:ident = $ty:literal) => { | ||
| ($name:ident = $ty:literal) => {{ | ||
| // Detect recursive type alias by checking if the name appears in the type string. | ||
| let name_str = stringify!($name); | ||
| let is_recursive = $ty.contains(name_str); | ||
| $crate::TypeAlias { | ||
| name: $crate::name(stringify!($name)), | ||
| name: $crate::name(name_str), | ||
| resolves_to: $crate::ty($ty), | ||
| recursive: is_recursive, | ||
| } | ||
| }; | ||
| }}; |
There was a problem hiding this comment.
Recursive-alias detection by substring is over-eager.
$ty.contains($name) matches any substring, not a token, so:
type_alias!("Tree" = "TreeNode[]")→ flagged recursive (false positive)type_alias!("User" = "UserId")→ flagged recursive (false positive)
Since fixtures drive the snapshot tests for the real recursive-alias rendering path in baml_codegen_python/src/objects/render/type_alias.rs, false positives can mask both regressions in the production recursive-alias detector and bugs in non-recursive alias rendering. A minimal fix is a word-boundary/token check (e.g., split on non-identifier chars and look for an exact element).
🛡️ Proposed fix
- ($name:literal = $ty:literal) => {{
- // Detect recursive type alias by checking if the name appears in the type string.
- let is_recursive = $ty.contains($name);
+ ($name:literal = $ty:literal) => {{
+ // Detect recursive type alias by checking if the name appears as a whole identifier
+ // token in the RHS (avoids false positives like "Tree" matching "TreeNode").
+ let is_recursive = $ty
+ .split(|c: char| !c.is_alphanumeric() && c != '_')
+ .any(|tok| tok == $name);
$crate::TypeAlias {
name: $crate::name($name),
resolves_to: $crate::ty($ty),
recursive: is_recursive,
}
}};
// Identifier name (for use in other macros)
($name:ident = $ty:literal) => {{
- // Detect recursive type alias by checking if the name appears in the type string.
let name_str = stringify!($name);
- let is_recursive = $ty.contains(name_str);
+ let is_recursive = $ty
+ .split(|c: char| !c.is_alphanumeric() && c != '_')
+ .any(|tok| tok == name_str);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@baml_language/crates/baml_codegen_tests/src/builders.rs` around lines 513 -
532, The recursive detection currently uses substring matching
($ty.contains($name)/contains(name_str)) which yields false positives; change
the check to tokenize the RHS type string on non-identifier characters and test
for an exact identifier match to the alias name. In both macro arms replace the
is_recursive assignment with a tokenized check (e.g., split $ty on chars where
!is_alphanumeric && != '_' and see if any token == $name or name_str) so
TypeAlias { ..., recursive: <token-equals-check> } uses exact identifier
equality rather than contains. Ensure you apply the same logic in the
($name:literal = $ty:literal) and ($name:ident = $ty:literal) branches so
TypeAlias, name(...), and resolves_to(...) remain unchanged.
Summary by CodeRabbit
New Features
Bug Fixes
Refactor
baml-pytobaml