Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ biome_css_syntax = "0.5.8"
biome_rowan = "0.5.8"
schemars = "1.2.0"

# Temporary files
tempfile = "3.15"

[package]
name = "apx"
version = "0.2.3"
Expand Down Expand Up @@ -152,6 +155,7 @@ rusqlite.workspace = true
opentelemetry-proto.workspace = true
prost.workspace = true
similar.workspace = true
tempfile.workspace = true

[dev-dependencies]
tempfile = "3.15"
tempfile.workspace = true
4 changes: 1 addition & 3 deletions src/apx/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import sys

from apx._core import get_bun_binary_path, run_cli

__version__ = "0.1.0"
from apx._core import get_bun_binary_path, run_cli, __version__


def main() -> None:
Expand Down
2 changes: 2 additions & 0 deletions src/apx/_core.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from pathlib import Path

__version__: str

def run_cli(args: list[str]) -> int: ...
def get_bun_binary_path() -> Path: ...
def generate_openapi(project_root: Path) -> bool: ...
Expand Down
38 changes: 38 additions & 0 deletions src/dev/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,30 @@ fn format_log_line(source: LogSource, message: &str) -> String {
format!("{timestamp} | {source:>4} | {message}")
}

/// Setup sitecustomize.py for Databricks SDK user-agent tracking.
/// This is non-critical telemetry - failures are silently ignored.
/// Returns the temp directory path if successful, None otherwise.
fn setup_sitecustomize() -> Option<PathBuf> {
let temp_dir = tempfile::tempdir().ok()?;
let sitecustomize_path = temp_dir.path().join("sitecustomize.py");
std::fs::write(
&sitecustomize_path,
r#"import os
try:
from databricks.sdk.core import with_user_agent_extra, with_product
from apx import __version__
if os.getenv('APX_UVICORN') == '1':
with_user_agent_extra('apx', __version__)
with_product('apx', __version__)
except Exception:
pass
"#,
)
.ok()?;
// Use into_path() to prevent automatic cleanup - dir persists for uvicorn's lifetime
Some(temp_dir.keep())
}

#[derive(Debug)]
pub struct ProcessManager {
frontend_child: Arc<Mutex<Option<Child>>>,
Expand Down Expand Up @@ -310,6 +334,9 @@ impl ProcessManager {
resolve_log_config(&self.dev_config, &self.app_slug, app_dir).await?;
let log_config = log_config_result.to_string_path();

// Setup sitecustomize.py for user-agent tracking (non-critical, best-effort)
let sitecustomize_dir = setup_sitecustomize();

// Run uvicorn via uv to ensure correct Python environment
let mut cmd = UvCommand::new("uvicorn").tokio_command();
cmd.args([
Expand All @@ -336,9 +363,20 @@ impl ProcessManager {
cmd.env("APX_DEV_SERVER_PORT", self.dev_server_port.to_string());
cmd.env("APX_DEV_SERVER_HOST", &self.host);
cmd.env("APX_DEV_TOKEN", &self.dev_token);
cmd.env("APX_UVICORN", "1");

// Force Python to flush stdout/stderr immediately (no buffering)
cmd.env("PYTHONUNBUFFERED", "1");

// Prepend sitecustomize dir to PYTHONPATH if setup succeeded (non-critical)
if let Some(sitecustomize_path) = sitecustomize_dir {
let pythonpath = match std::env::var("PYTHONPATH") {
Ok(existing) => format!("{}:{}", sitecustomize_path.display(), existing),
Err(_) => sitecustomize_path.display().to_string(),
};
cmd.env("PYTHONPATH", pythonpath);
}

// Apply dotenv variables
let vars = self.dotenv_vars.lock().await;
for (key, value) in vars.iter() {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ fn get_dotenv_vars() -> PyResult<HashMap<String, String>> {
#[pymodule]
fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
init_tracing();
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
m.add_function(wrap_pyfunction!(run_cli, m)?)?;
m.add_function(wrap_pyfunction!(get_bun_binary_path, m)?)?;
m.add_function(wrap_pyfunction!(generate_openapi_py, m)?)?;
Expand Down