Skip to content

Commit b4ddb6f

Browse files
Enhance Type Builder API (#2177)
Fixes for #2050 & #2057 <!-- ELLIPSIS_HIDDEN --> ---- > [!IMPORTANT] > Enhance `TypeBuilder` API with new methods for property management and class resetting, and update tests across Python, TypeScript, and Ruby. > > - **Behavior**: > - Add `reset()` method to `TypeBuilder` and `ClassBuilder` in `mod.rs`, `type_builder.rs`, and `type_builder.py` to clear properties and classes. > - Add `remove_property()` and `list_properties()` methods to `ClassBuilder` in `mod.rs` and `type_builder.rs` for property management. > - Implement `get_type()` and `set_type()` in `ClassPropertyBuilder` in `type_builder.rs`. > - **Tests**: > - Update Python tests in `test_typebuilder.py` to cover `reset()`, `remove_property()`, and `list_properties()`. > - Update TypeScript tests in `dynamic-types.test.ts` to verify new methods and dynamic type handling. > - Add Ruby tests in `test_typebuilder.rb` for `reset()` and `remove_property()` methods. > - **Misc**: > - Update TypeScript and Ruby integration files to reflect new `TypeBuilder` functionalities. > - Add documentation examples in `typebuilder.mdx` for new methods. > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=BoundaryML%2Fbaml&utm_source=github&utm_medium=referral)<sup> for 51c4af9. You can [customize](https://app.ellipsis.dev/BoundaryML/settings/summaries) this summary. It will automatically update as commits are pushed.</sup> <!-- ELLIPSIS_HIDDEN -->
1 parent 63f45d4 commit b4ddb6f

24 files changed

Lines changed: 778 additions & 53 deletions

File tree

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ $RECYCLE.BIN/
7979
.nfs*
8080
.node_repl_history
8181
.npm
82+
.pnpm-store/
8283
.nuxt
8384
.nyc_output
8485
.nyc_output/

‎engine/baml-runtime/src/type_builder/mod.rs‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{
22
fmt,
3+
ops::Deref,
34
sync::{Arc, Mutex},
45
};
56

@@ -104,6 +105,18 @@ impl ClassBuilder {
104105
}
105106
}
106107

108+
// TODO: Figure out captured lifetime issue and return Iterator.
109+
// Iterator that holds mutex lock seems tricky.
110+
pub fn list_properties_key_value(&self) -> Vec<(String, ClassPropertyBuilder)> {
111+
self.properties
112+
.lock()
113+
.unwrap()
114+
.iter()
115+
.map(|(name, prop)| (name.clone(), prop.lock().unwrap().deref().to_owned()))
116+
.collect()
117+
}
118+
119+
// TODO: Unify function above and this one (split because of CFFI).
107120
pub fn list_properties(&self) -> Vec<String> {
108121
let properties = self.properties.lock().unwrap();
109122
properties.keys().cloned().collect()
@@ -123,6 +136,15 @@ impl ClassBuilder {
123136
}))
124137
}))
125138
}
139+
140+
pub fn remove_property(&self, name: &str) {
141+
let mut properties = self.properties.lock().unwrap();
142+
properties.shift_remove(name);
143+
}
144+
145+
pub fn reset(&self) {
146+
self.properties.lock().unwrap().clear();
147+
}
126148
}
127149

128150
#[derive(Debug, Clone)]
@@ -500,6 +522,14 @@ impl TypeBuilder {
500522
}
501523
}
502524

525+
pub fn reset(&self) {
526+
self.classes.lock().unwrap().clear();
527+
self.enums.lock().unwrap().clear();
528+
self.type_aliases.lock().unwrap().clear();
529+
self.recursive_type_aliases.lock().unwrap().clear();
530+
self.recursive_classes.lock().unwrap().clear();
531+
}
532+
503533
pub fn upsert_class(&self, name: &str) -> Arc<Mutex<ClassBuilder>> {
504534
Arc::clone(
505535
self.classes

‎engine/generators/languages/python/src/generated_types.rs‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,13 @@ mod type_builder {
180180
/// return self._bldr.property(name).type(type)
181181
///
182182
/// def list_properties(self) -> typing.List[typing.Tuple[str, {{ class.class_property_type() }}]]:
183-
/// return [(name, self._bldr.property(name)) for name in self._properties]
183+
/// return self._bldr.list_properties()
184+
///
185+
/// def remove_property(self, name: str) -> None:
186+
/// self._bldr.remove_property(name)
187+
///
188+
/// def reset(self) -> None:
189+
/// self._bldr.reset()
184190
///
185191
/// {% else %}
186192
/// def list_properties(self) -> typing.List[typing.Tuple[str, {{ class.class_property_type() }}]]:

‎engine/generators/languages/typescript/src/_templates/type_builder.ts.j2‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ export default class TypeBuilder {
3737
{% endfor %}
3838
}
3939

40+
reset(): void {
41+
this.tb.reset();
42+
// TODO: This should happen in Rust. Problem is, when we construct the
43+
// typebuilder we instantiate class builders once and it seems to make
44+
// a JS copy, bypassing the Rust side? In Python however, every time we
45+
// access a class builder with @property, we get a new instance that
46+
// wraps over the Rust type builder, so we only need to call tb.reset().
47+
// In JS it's not possible unless we refactor the way class builders are
48+
// accessed.
49+
{% for cls in classes %}{% if cls.dynamic %} this.{{cls.name}}.reset(); {% endif %}{% endfor %}
50+
}
51+
4052
__tb() {
4153
return this.tb._tb();
4254
}

‎engine/language_client_python/python_src/baml_py/baml_py.pyi‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ class BamlSpan:
275275

276276
class TypeBuilder:
277277
def __init__(self) -> None: ...
278+
def reset(self) -> None: ...
278279
def enum(self, name: str) -> EnumBuilder: ...
279280
def class_(self, name: str) -> ClassBuilder: ...
280281
def string(self) -> FieldType: ...
@@ -470,6 +471,7 @@ class ClientRegistry:
470471
class FieldType:
471472
def list(self) -> FieldType: ...
472473
def optional(self) -> FieldType: ...
474+
def __eq__(self, other: object) -> bool: ...
473475

474476
class EnumBuilder:
475477
def value(self, name: str) -> EnumValueBuilder: ...
@@ -483,10 +485,14 @@ class EnumValueBuilder:
483485

484486
class ClassBuilder:
485487
def field(self) -> FieldType: ...
488+
def list_properties(self) -> List[Tuple[str, ClassPropertyBuilder]]: ...
486489
def property(self, name: str) -> ClassPropertyBuilder: ...
490+
def remove_property(self, name: str) -> None: ...
491+
def reset(self) -> None: ...
487492

488493
class ClassPropertyBuilder:
489494
def type(self, field_type: FieldType) -> ClassPropertyBuilder: ...
495+
def get_type(self) -> FieldType: ...
490496
def alias(self, alias: Optional[str]) -> ClassPropertyBuilder: ...
491497
def description(self, description: Optional[str]) -> ClassPropertyBuilder: ...
492498

‎engine/language_client_python/python_src/baml_py/type_builder.py‎

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ def __init__(
1919
self.__tb = _TypeBuilder()
2020
self.__runtime = runtime
2121

22+
def reset(self):
23+
self.__tb.reset()
24+
2225
def __str__(self) -> str:
2326
"""
2427
returns a comprehensive string representation of the typebuilder.
@@ -111,29 +114,22 @@ def add_baml(self, baml: str):
111114
class NewClassBuilder:
112115
def __init__(self, tb: _TypeBuilder, name: str):
113116
self.__bldr = tb.class_(name)
114-
self.__properties: typing.Set[str] = set()
115-
self.__props = NewClassProperties(self.__bldr, self.__properties)
116117

117118
def type(self) -> FieldType:
118119
return self.__bldr.field()
119120

120121
def list_properties(self) -> typing.List[typing.Tuple[str, "ClassPropertyBuilder"]]:
121-
return [
122-
(name, ClassPropertyBuilder(self.__bldr.property(name)))
123-
for name in self.__properties
124-
]
122+
return self.__bldr.list_properties()
123+
124+
def reset(self):
125+
self.__bldr.reset()
126+
127+
def remove_property(self, name: str):
128+
self.__bldr.remove_property(name)
125129

126130
def add_property(self, name: str, type: FieldType) -> "ClassPropertyBuilder":
127-
if name in self.__properties:
128-
raise ValueError(f"Property {name} already exists.")
129-
# BUG: we don't add to self.__properties here
130-
# correct fix is to implement this logic in rust, not python
131131
return ClassPropertyBuilder(self.__bldr.property(name).type(type))
132132

133-
@property
134-
def props(self) -> "NewClassProperties":
135-
return self.__props
136-
137133

138134
class ClassPropertyBuilder:
139135
def __init__(self, bldr: _ClassPropertyBuilder):
@@ -153,17 +149,6 @@ def __init__(self, bldr: _ClassPropertyBuilder):
153149
self.__bldr = bldr
154150

155151

156-
class NewClassProperties:
157-
def __init__(self, cls_bldr: ClassBuilder, properties: typing.Set[str]):
158-
self.__bldr = cls_bldr
159-
self.__properties = properties
160-
161-
def __getattr__(self, name: str) -> "ClassPropertyBuilder":
162-
if name not in self.__properties:
163-
raise AttributeError(f"Property {name} not found.")
164-
return ClassPropertyBuilder(self.__bldr.property(name))
165-
166-
167152
class NewEnumBuilder:
168153
def __init__(self, tb: _TypeBuilder, name: str):
169154
self.__bldr = tb.enum(name)

‎engine/language_client_python/src/types/type_builder.rs‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::ops::Deref;
2+
13
use baml_runtime::type_builder::{self, WithMeta};
24
use baml_types::{ir_type::UnionConstructor, BamlValue};
35
use pyo3::{
@@ -37,6 +39,10 @@ impl TypeBuilder {
3739
type_builder::TypeBuilder::new().into()
3840
}
3941

42+
pub fn reset(&self) {
43+
self.inner.reset();
44+
}
45+
4046
/// provides a detailed string representation of the typebuilder for python users.
4147
///
4248
/// this method exposes the rust-implemented string formatting to python, ensuring
@@ -149,6 +155,10 @@ impl FieldType {
149155
pub fn optional(&self) -> FieldType {
150156
self.inner.lock().unwrap().clone().as_optional().into()
151157
}
158+
159+
pub fn __eq__(&self, other: &FieldType) -> bool {
160+
self.inner.lock().unwrap().deref() == other.inner.lock().unwrap().deref()
161+
}
152162
}
153163

154164
#[pymethods]
@@ -213,6 +223,24 @@ impl ClassBuilder {
213223
baml_types::TypeIR::class(&self.name).into()
214224
}
215225

226+
pub fn list_properties(&self) -> Vec<(String, ClassPropertyBuilder)> {
227+
self.inner
228+
.lock()
229+
.unwrap()
230+
.list_properties_key_value()
231+
.into_iter()
232+
.map(|(name, prop_builder)| (name, prop_builder.into()))
233+
.collect()
234+
}
235+
236+
pub fn remove_property(&self, name: &str) {
237+
self.inner.lock().unwrap().remove_property(name);
238+
}
239+
240+
pub fn reset(&self) {
241+
self.inner.lock().unwrap().reset();
242+
}
243+
216244
pub fn property(&self, name: &str) -> ClassPropertyBuilder {
217245
self.inner.lock().unwrap().upsert_property(name).into()
218246
}
@@ -228,6 +256,17 @@ impl ClassPropertyBuilder {
228256
self.inner.clone().into()
229257
}
230258

259+
pub fn get_type(&self) -> PyResult<FieldType> {
260+
self.inner
261+
.lock()
262+
.unwrap()
263+
.r#type()
264+
.map(FieldType::from)
265+
.ok_or_else(|| BamlError::from_anyhow(anyhow::anyhow!(
266+
"attempted to read a property that has no defined type, this is likely an internal bug"
267+
)))
268+
}
269+
231270
#[pyo3(signature = (alias = None))]
232271
pub fn alias(&self, alias: Option<&str>) -> Self {
233272
self.inner.lock().unwrap().with_meta(

‎engine/language_client_ruby/ext/ruby_ffi/src/types/type_builder.rs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ impl TypeBuilder {
4040
}
4141
}
4242

43+
pub fn reset(&self) {
44+
self.inner.reset();
45+
}
46+
4347
pub fn r#enum(&self, name: String) -> EnumBuilder {
4448
EnumBuilder {
4549
inner: self.inner.upsert_enum(name.as_str()),
@@ -141,6 +145,8 @@ impl TypeBuilder {
141145
cls.define_singleton_method("new", function!(TypeBuilder::new, 0))?;
142146
cls.define_method("to_s", method!(TypeBuilder::to_s, 0))?;
143147
cls.define_method("enum", method!(TypeBuilder::r#enum, 1))?;
148+
// TODO: Not exposed, Ruby doesn't work right now.
149+
// cls.define_method("reset", method!(TypeBuilder::reset, 0))?;
144150
// "class" is used by Kernel: https://ruby-doc.org/core-3.0.2/Kernel.html#method-i-class
145151
cls.define_method("class_", method!(TypeBuilder::class, 1))?;
146152
cls.define_method("list", method!(TypeBuilder::list, 1))?;
@@ -256,6 +262,14 @@ impl EnumValueBuilder {
256262
}
257263

258264
impl ClassBuilder {
265+
pub fn reset(&self) {
266+
self.inner.lock().unwrap().reset();
267+
}
268+
269+
pub fn remove_property(&self, name: String) {
270+
self.inner.lock().unwrap().remove_property(name.as_str());
271+
}
272+
259273
pub fn field(&self) -> FieldType {
260274
baml_types::TypeIR::class(&self.name).into()
261275
}
@@ -273,6 +287,9 @@ impl ClassBuilder {
273287

274288
cls.define_method("field", method!(ClassBuilder::field, 0))?;
275289
cls.define_method("property", method!(ClassBuilder::property, 1))?;
290+
// TODO: Not exposed, Ruby doesn't work right now.
291+
// cls.define_method("reset", method!(ClassBuilder::reset, 0))?;
292+
// cls.define_method("remove_property", method!(ClassBuilder::remove_property, 1))?;
276293

277294
Ok(())
278295
}

‎engine/language_client_typescript/native.d.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,16 @@ export declare class BamlVideo {
6161
}
6262

6363
export declare class ClassBuilder {
64+
listProperties(): unknown[]
65+
removeProperty(name: string): void
66+
reset(): void
6467
field(): FieldType
6568
property(name: string): ClassPropertyBuilder
6669
}
6770

6871
export declare class ClassPropertyBuilder {
6972
setType(fieldType: FieldType): ClassPropertyBuilder
73+
getType(): FieldType
7074
alias(alias?: string | undefined | null): ClassPropertyBuilder
7175
description(description?: string | undefined | null): ClassPropertyBuilder
7276
}
@@ -103,6 +107,7 @@ export declare class EnumValueBuilder {
103107
export declare class FieldType {
104108
list(): FieldType
105109
optional(): FieldType
110+
equals(other: FieldType): boolean
106111
}
107112

108113
export declare class FunctionLog {
@@ -215,6 +220,7 @@ export declare class TraceStats {
215220

216221
export declare class TypeBuilder {
217222
constructor()
223+
reset(): void
218224
getEnum(name: string): EnumBuilder
219225
getClass(name: string): ClassBuilder
220226
list(inner: FieldType): FieldType

0 commit comments

Comments
 (0)