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
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ def test_ibis_available_in_context():
import ibis

from boring_semantic_layer import from_yaml
from boring_semantic_layer.utils import safe_eval
from boring_semantic_layer.safe_eval import safe_eval

# Load models
models = from_yaml(
Expand Down
2 changes: 1 addition & 1 deletion src/boring_semantic_layer/agents/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from boring_semantic_layer.agents.utils.chart_handler import generate_chart_with_data
from boring_semantic_layer.agents.utils.prompts import load_prompt
from boring_semantic_layer.utils import safe_eval
from boring_semantic_layer.safe_eval import safe_eval
from boring_semantic_layer.yaml import from_yaml


Expand Down
2 changes: 1 addition & 1 deletion src/boring_semantic_layer/chart/md_parser/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from boring_semantic_layer import to_semantic_table
from boring_semantic_layer._xorq import api as xo
from boring_semantic_layer.utils import safe_eval
from boring_semantic_layer.safe_eval import safe_eval


class QueryExecutor:
Expand Down
74 changes: 74 additions & 0 deletions src/boring_semantic_layer/io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""YAML/URL loading helpers for model configuration files."""

from __future__ import annotations

from pathlib import Path

import yaml


def _is_url(path: str | Path | None) -> bool:
"""Check if a path is a URL."""
if path is None:
return False
from urllib.parse import urlparse

parsed = urlparse(str(path))
return parsed.scheme in ("http", "https")


def _fetch_url_content(url: str) -> str:
"""Fetch content from a URL.

Args:
url: The URL to fetch

Returns:
The content as a string

Raises:
ValueError: If the fetch fails
"""
import urllib.error
import urllib.request

try:
with urllib.request.urlopen(url, timeout=30) as response:
return response.read().decode("utf-8")
except urllib.error.HTTPError as e:
raise ValueError(f"HTTP Error {e.code}: {e.reason} for URL: {url}") from e
except urllib.error.URLError as e:
raise ValueError(f"URL Error: {e.reason} for URL: {url}") from e
except Exception as e:
raise ValueError(f"Failed to fetch URL {url}: {e}") from e


def read_yaml_file(yaml_path: str | Path) -> dict:
"""Read and parse YAML file into dict. Supports local files and URLs.

Args:
yaml_path: Path to local file or URL (http:// or https://)

Returns:
Parsed YAML content as dict
"""
try:
if _is_url(yaml_path):
content_str = _fetch_url_content(str(yaml_path))
content = yaml.safe_load(content_str)
else:
yaml_path = Path(yaml_path)
if not yaml_path.exists():
raise FileNotFoundError(f"YAML file not found: {yaml_path}")

with open(yaml_path) as f:
content = yaml.safe_load(f)

if not isinstance(content, dict):
raise ValueError(f"YAML file must contain a dict, got: {type(content)}")

return content
except (FileNotFoundError, ValueError):
raise
except Exception as e:
raise ValueError(f"Failed to parse YAML file {yaml_path}: {e}") from e
2 changes: 1 addition & 1 deletion src/boring_semantic_layer/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from ._xorq import HAS_XORQ
from ._xorq import Profile as XorqProfile
from .utils import read_yaml_file
from .io import read_yaml_file


class ProfileError(Exception):
Expand Down
2 changes: 1 addition & 1 deletion src/boring_semantic_layer/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from toolz import curry

from .errors import QueryError, unwrap_or_raise
from .utils import safe_eval
from .safe_eval import safe_eval


def _get_ibis_api():
Expand Down
Loading
Loading