Repository: tensorzero/tensorzero (language: rust)

Issue:
Normalize interface for new client
```py
## Sync + HTTP

client = TensorZeroGateway.build_http(gateway_url=...)
# or
with TensorZeroGateway.build_http(gateway_url=...) as client:

## Async + HTTP

client = await AsyncTensorZeroGateway.build_http(gateway_url=...)
# or
async with await AsyncTensorZeroGateway.build_http(gateway_url=...) as client:

## Sync + Embedded

client = TensorZeroGateway.build_embedded(config_path=..., clickhouse_url=...)
# or
with TensorZeroGateway.build_embedded(...) as client:		
		
## Async + Embedded

client = await AsyncTensorZeroGateway.build_embedded(config_path=..., clickhouse_url=...)
# or
async with await AsyncTensorZeroGateway.build_embedded(config_path=..., clickhouse_url=...) as client:
```

Notably, we preemptively make `AsyncTensorZeroGateway.build_http` also return a future, in case we need that short of functionality later.

For the old constructor-style setup:

```
Deprecation Warning: Please initialize your gateway with `TensorZeroGateway.build_embedded` or `TensorZeroGateway.build_http`. This constructor will be removed in a future version.
```

```
Deprecation Warning: Please initialize your gateway with `AsyncTensorZeroGateway.build_embedded` or `AsyncTensorZeroGateway.build_http`. This constructor will be removed in a future version.
```


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 57207ca03..00247fdb1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2985,6 +2985,7 @@ dependencies = [
  "serde_json",
  "tensorzero",
  "tokio",
+ "tracing",
  "url",
  "uuid",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index b4b974c00..a699434c6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,6 +24,7 @@ reqwest-eventsource = "0.6.0"
 async-stream = "0.3.5"
 tokio-stream = "0.1.15"
 tokio = { version = "1.38.1", features = ["full"] }
+tracing = { version = "0.1.40", features = ["log"] }
 pyo3 = { version = "0.23.3", features = ["experimental-async"] }
 axum = { version = "0.7.5", features = ["macros"] }
 futures = "0.3.30"
diff --git a/clients/python-pyo3/Cargo.toml b/clients/python-pyo3/Cargo.toml
index 56d6e0837..6dbbbbd43 100644
--- a/clients/python-pyo3/Cargo.toml
+++ b/clients/python-pyo3/Cargo.toml
@@ -24,6 +24,7 @@ tensorzero_rust = { package = "tensorzero", path = "../rust", features = [
     "pyo3",
 ] }
 tokio.workspace = true
+tracing = { workspace = true, features = ["release_max_level_debug"] }
 url.workspace = true
 uuid.workspace = true
 
diff --git a/clients/python-pyo3/src/lib.rs b/clients/python-pyo3/src/lib.rs
index 84911bf99..25ac31449 100644
--- a/clients/python-pyo3/src/lib.rs
+++ b/clients/python-pyo3/src/lib.rs
@@ -215,8 +215,8 @@ impl BaseTensorZeroGateway {
 #[pyclass(extends=BaseTensorZeroGateway)]
 /// A synchronous client for a TensorZero gateway.
 ///
-/// To connect to a running HTTP gateway, call `TensorZeroGateway(base_url = "http://gateway_url")`
-/// To create an embedded gateway, call `TensorZeroGateway.create_embedded_gateway(config_path = "/path/to/tensorzero.toml")`
+/// To connect to a running HTTP gateway, call `TensorZeroGateway.build_http(base_url = "http://gateway_url")`
+/// To create an embedded gateway, call `TensorZeroGateway.build_embedded(config_path = "/path/to/tensorzero.toml", clickhouse_url = "http://clickhouse_url")`
 struct TensorZeroGateway {}
 
 /// Calls `tokio::Runtime::block_on` without holding the Python GIL.
@@ -354,9 +354,27 @@ impl TensorZeroGateway {
         base_url: &str,
         timeout: Option<u64>,
     ) -> PyResult<(Self, BaseTensorZeroGateway)> {
+        tracing::warn!("TensorZeroGateway.__init__ is deprecated. Use TensorZeroGateway.build_http or TensorZeroGateway.build_embedded instead.");
         Ok((Self {}, BaseTensorZeroGateway::new(py, base_url, timeout)?))
     }
 
+    #[classmethod]
+    #[pyo3(signature = (gateway_url, *, timeout=None))]
+    /// Initialize the TensorZero client, using the HTTP gateway.
+    /// :param gateway_url: The base URL of the TensorZero gateway. Example: "http://localhost:3000"
+    /// :param timeout: The timeout for the HTTP client in seconds. If not provided, no timeout will be set.
+    /// :return: A `TensorZeroGateway` instance configured to use the HTTP gateway.
+    fn build_http(
+        cls: &Bound<'_, PyType>,
+        gateway_url: &str,
+        timeout: Option<u64>,
+    ) -> PyResult<Py<TensorZeroGateway>> {
+        let client = BaseTensorZeroGateway::new(cls.py(), gateway_url, timeout)?;
+        let instance = PyClassInitializer::from(client).add_subclass(TensorZeroGateway {});
+        Py::new(cls.py(), instance)
+    }
+
+    /// **Deprecated** (use `build_http` or `build_embedded` instead)
     /// Initialize the TensorZero client.
     ///
     /// :param base_url: The base URL of the TensorZero gateway. Example: "http://localhost:3000"
@@ -395,7 +413,7 @@ impl TensorZeroGateway {
     /// :param config_path: The path to the TensorZero configuration file. Example: "tensorzero.toml"
     /// :param clickhouse_url: The URL of the ClickHouse instance to use for the gateway. If observability is disabled in the config, this can be `None`
     /// :return: A `TensorZeroGateway` instance configured to use an embedded gateway.
-    fn create_embedded_gateway(
+    fn build_embedded(
         cls: &Bound<'_, PyType>,
         config_path: &str,
         clickhouse_url: Option<String>,
@@ -575,9 +593,29 @@ impl AsyncTensorZeroGateway {
         base_url: &str,
         timeout: Option<u64>,
     ) -> PyResult<(Self, BaseTensorZeroGateway)> {
+        tracing::warn!("AsyncTensorZeroGateway.__init__ is deprecated. Use AsyncTensorZeroGateway.build_http or AsyncTensorZeroGateway.build_embedded instead.");
         Ok((Self {}, BaseTensorZeroGateway::new(py, base_url, timeout)?))
     }
 
+    #[classmethod]
+    #[pyo3(signature = (gateway_url, *, timeout=None))]
+    fn build_http<'a>(
+        cls: &Bound<'a, PyType>,
+        gateway_url: &str,
+        timeout: Option<u64>,
+    ) -> PyResult<Bound<'a, PyAny>> {
+        let gateway_url = gateway_url.to_string();
+        pyo3_async_runtimes::tokio::future_into_py(cls.py(), async move {
+            Python::with_gil(|py| {
+                let client = BaseTensorZeroGateway::new(py, &gateway_url, timeout)?;
+                let instance =
+                    PyClassInitializer::from(client).add_subclass(AsyncTensorZeroGateway {});
+                Py::new(py, instance)
+            })
+        })
+    }
+
+    /// **Deprecated** (use `build_http` or `build_embedded` instead)
     /// Initialize the TensorZero client.
     ///
     /// :param base_url: The base URL of the TensorZero gateway. Example: "http://localhost:3000"
@@ -623,7 +661,7 @@ impl AsyncTensorZeroGateway {
     /// :param config_path: The path to the TensorZero configuration file. Example: "tensorzero.toml"
     /// :param clickhouse_url: The URL of the ClickHouse instance to use for the gateway. If observability is disabled in the config, this can be `None`
     /// :return: A `Future` that resolves to an `AsyncTensorZeroGateway` instance configured to use an embedded gateway.
-    fn create_embedded_gateway<'a>(
+    fn build_embedded<'a>(
         // This is a classmethod, so it receives the class object as a parameter.
         cls: &Bound<'a, PyType>,
         config_path: &str,
diff --git a/clients/python-pyo3/tensorzero/tensorzero.pyi b/clients/python-pyo3/tensorzero/tensorzero.pyi
index 6568e00ea..d2be2e8d7 100644
--- a/clients/python-pyo3/tensorzero/tensorzero.pyi
+++ b/clients/python-pyo3/tensorzero/tensorzero.pyi
@@ -18,7 +18,7 @@ from tensorzero import (
 )
 
 class BaseTensorZeroGateway:
-    def __init__(self, base_url: str): ...
+    def __init__(self, base_url: str, *, timeout: Optional[float] = None): ...
     def inference(
         self,
         *,
@@ -60,6 +60,28 @@ class TensorZeroGateway(BaseTensorZeroGateway):
         :param base_url: The base URL of the TensorZero gateway. Example: "http://localhost:3000"
         """
 
+    @classmethod
+    def build_http(
+        self, gateway_url: str, *, timeout: Optional[float] = None
+    ) -> "TensorZeroGateway":
+        """
+        Build a TensorZeroGateway instance.
+
+        :param gateway_url: The base URL of the TensorZero gateway. Example: "http://localhost:3000"
+        :param timeout: (Optional) The timeout for the HTTP request.
+        """
+
+    @classmethod
+    def build_embedded(
+        self, *, config_path: Optional[str] = None, clickhouse_url: Optional[str] = None
+    ) -> "TensorZeroGateway":
+        """
+        Build a TensorZeroGateway instance.
+
+        :param config_path: (Optional) The path to the TensorZero configuration file.
+        :param clickhouse_url: (Optional) The URL of the ClickHouse database.
+        """
+
     def inference(
         self,
         *,
@@ -172,6 +194,28 @@ class AsyncTensorZeroGateway(BaseTensorZeroGateway):
         :param base_url: The base URL of the TensorZero gateway. Example: "http://localhost:3000"
         """
 
+    @classmethod
+    async def build_http(
+        self, gateway_url: str, *, timeout: Optional[float] = None
+    ) -> "AsyncTensorZeroGateway":
+        """
+        Build an AsyncTensorZeroGateway instance.
+
+        :param gateway_url: (Required) The base URL of the TensorZero gateway. Example: "http://localhost:3000"
+        :param timeout: (Optional) The timeout for the HTTP request.
+        """
+
+    @classmethod
+    async def build_embedded(
+        self, *, config_path: Optional[str] = None, clickhouse_url: Optional[str] = None
+    ) -> "AsyncTensorZeroGateway":
+        """
+        Build an AsyncTensorZeroGateway instance.
+
+        :param config_path: (Optional) The path to the TensorZero configuration file.
+        :param clickhouse_url: (Optional) The URL of the ClickHouse database.
+        """
+
     async def inference(  # type: ignore[override]
         self,
         *,
diff --git a/tensorzero-internal/Cargo.toml b/tensorzero-internal/Cargo.toml
index 4a2ec958e..6d3a4cddb 100644
--- a/tensorzero-internal/Cargo.toml
+++ b/tensorzero-internal/Cargo.toml
@@ -74,7 +74,7 @@ strum_macros = "0.26.3"
 tokio = { version = "1.38.1", features = ["full"] }
 tokio-stream = { workspace = true }
 toml = "0.8.15"
-tracing = { version = "0.1.40", features = ["log", "release_max_level_debug"] }
+tracing.workspace = true
 tracing-subscriber = { version = "0.3.18", features = [
     "env-filter",
     "fmt",


Test patch:
diff --git a/clients/python-pyo3/tests/test_client.py b/clients/python-pyo3/tests/test_client.py
index 64a3f0039..5d9f3ad67 100644
--- a/clients/python-pyo3/tests/test_client.py
+++ b/clients/python-pyo3/tests/test_client.py
@@ -56,10 +56,12 @@ class ClientType(Enum):
 @pytest_asyncio.fixture(params=[ClientType.HttpGateway, ClientType.EmbeddedGateway])
 async def async_client(request):
     if request.param == ClientType.HttpGateway:
-        async with AsyncTensorZeroGateway("http://localhost:3000") as client:
+        async with await AsyncTensorZeroGateway.build_http(
+            "http://localhost:3000"
+        ) as client:
             yield client
     else:
-        async with await AsyncTensorZeroGateway.create_embedded_gateway(
+        async with await AsyncTensorZeroGateway.build_embedded(
             config_path=TEST_CONFIG_PATH,
             clickhouse_url="http://localhost:8123/tensorzero-python-e2e",
         ) as client:
@@ -624,10 +626,10 @@ async def test_async_error():
 @pytest.fixture(params=[ClientType.HttpGateway, ClientType.EmbeddedGateway])
 def sync_client(request):
     if request.param == ClientType.HttpGateway:
-        with TensorZeroGateway("http://localhost:3000") as client:
+        with TensorZeroGateway.build_http("http://localhost:3000") as client:
             yield client
     else:
-        with TensorZeroGateway.create_embedded_gateway(
+        with TensorZeroGateway.build_embedded(
             config_path=TEST_CONFIG_PATH,
             clickhouse_url="http://localhost:8123/tensorzero-python-e2e",
         ) as client: