Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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: 4 additions & 2 deletions nixtla/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
__version__ = "0.7.2"
__all__ = ["NixtlaClient"]
from importlib.metadata import version
from .nixtla_client import NixtlaClient

__version__ = version("nixtla")
__all__ = ["NixtlaClient"]
52 changes: 37 additions & 15 deletions nixtla/scripts/snowflake_install_nixtla.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,33 +582,50 @@ def detect_package_installer() -> tuple[list[str], bool]:
)


def package_and_upload_nixtla(session: Session, stage: str) -> None:
def package_and_upload_nixtla(
session: Session, stage: str, fallback_package_source: Optional[str] = None
) -> None:
"""
Package nixtla client and upload to Snowflake stage.

Args:
session: Active Snowflake session
stage: Stage name to upload to
fallback_package_source: Local path to nixtla package (e.g. project root)
used as a fallback when the current version is not yet on PyPI.
"""
with TemporaryDirectory() as tmpdir:
# Import version from nixtla package
from nixtla import __version__ as nixtla_version

# Detect package installer
pip_cmd, use_uv = detect_package_installer()

# Install packages with appropriate flags
# UV uses --target instead of -t
install_args = pip_cmd + [
# Build base install args (shared between PyPI and fallback attempts)
base_args = pip_cmd + [
"--target" if use_uv else "-t",
tmpdir,
f"nixtla=={nixtla_version}",
"utilsforecast",
"httpx",
"--no-deps", # Avoid pulling in heavy things like pandas/numpy into the ZIP
]
extra_deps = ["utilsforecast", "httpx"]
no_deps_flag = ["--no-deps"] # Avoid pulling in heavy things like pandas/numpy

# Try the released PyPI version first
pypi_args = (
base_args + [f"nixtla=={nixtla_version}"] + extra_deps + no_deps_flag
)
pip_result = subprocess.run(pypi_args)

if pip_result.returncode != 0 and fallback_package_source is not None:
print(
f"[yellow]nixtla=={nixtla_version} not found on PyPI, "
f"falling back to local package: {fallback_package_source}[/yellow]"
)
fallback_args = (
base_args + [fallback_package_source] + extra_deps + no_deps_flag
)
pip_result = subprocess.run(fallback_args, check=True)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need fallback approach when the package cannot be found in the package repository.


subprocess.run(install_args, check=True)
# Check whether the pip installation run successfully
pip_result.check_returncode()

# Create zip archive
shutil.make_archive(os.path.join(tmpdir, "nixtla"), "zip", tmpdir)
Expand Down Expand Up @@ -749,7 +766,7 @@ def create_udtfs(session: Session, config: DeploymentConfig) -> None:
)
class ForecastUDTF:
def __init__(self):
import _snowflake
import _snowflake # type: ignore
from nixtla import NixtlaClient

token = _snowflake.get_generic_secret_string(SECRET_API_KEY)
Expand Down Expand Up @@ -1045,7 +1062,7 @@ def _parse_metrics(self, metrics):
)
class AnomalyDetectionUDTF:
def __init__(self):
import _snowflake
import _snowflake # type: ignore
from nixtla import NixtlaClient

token = _snowflake.get_generic_secret_string(SECRET_API_KEY)
Expand Down Expand Up @@ -1130,7 +1147,7 @@ class ExplainUDTF:
"""UDTF for computing feature contributions (SHAP values) in long format."""

def __init__(self):
import _snowflake
import _snowflake # type: ignore
from nixtla import NixtlaClient

token = _snowflake.get_generic_secret_string(SECRET_API_KEY)
Expand Down Expand Up @@ -1319,7 +1336,7 @@ def nixtla_finetune(
params: Optional[dict] = None,
max_series: int = 1000,
) -> str:
import _snowflake
import _snowflake # type: ignore
from snowflake.snowpark import functions as F
from nixtla import NixtlaClient

Expand Down Expand Up @@ -1865,6 +1882,7 @@ def deploy_snowflake_core(
deploy_procedures: bool = True,
deploy_finetune: bool = True,
deploy_examples: bool = True,
fallback_package_source: Optional[str] = None,
) -> DeploymentConfig:
"""
Core deployment logic without user interaction.
Expand All @@ -1883,6 +1901,8 @@ def deploy_snowflake_core(
deploy_procedures: Whether to create stored procedures
deploy_finetune: Whether to create finetune stored procedure
deploy_examples: Whether to load example datasets
fallback_package_source: Local path to nixtla package (e.g. project root)
used as a fallback when the current version is not yet on PyPI.

Returns:
DeploymentConfig that was used for deployment
Expand All @@ -1895,7 +1915,9 @@ def deploy_snowflake_core(
create_security_integration(session, config, api_key, skip_confirmation=True)

if deploy_package:
package_and_upload_nixtla(session, config.stage)
package_and_upload_nixtla(
session, config.stage, fallback_package_source=fallback_package_source
)

if deploy_udtfs:
create_udtfs(session, config)
Expand Down
7 changes: 7 additions & 0 deletions nixtla_tests/snowflake/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@
import os
import uuid
from dataclasses import dataclass
from pathlib import Path
from typing import Generator

import pandas as pd
import pytest

# Resolve the project root (where pyproject.toml lives) so tests install
# the local package instead of fetching an (unreleased) version from PyPI.
_PROJECT_ROOT = str(Path(__file__).resolve().parents[2])
from dotenv import load_dotenv
from snowflake.snowpark import Session

Expand Down Expand Up @@ -338,6 +343,7 @@ def deployed_with_api_endpoint(
deploy_procedures=True,
deploy_finetune=False, # Skip finetune to speed up tests
deploy_examples=False, # Load examples separately to get DataFrames
fallback_package_source=_PROJECT_ROOT,
)

yield config
Expand Down Expand Up @@ -396,6 +402,7 @@ def deployed_with_tsmp_endpoint(
deploy_procedures=True,
deploy_finetune=False, # Skip finetune to speed up tests
deploy_examples=False, # Load examples separately if needed
fallback_package_source=_PROJECT_ROOT,
)

yield config
Expand Down
18 changes: 6 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[build-system]
requires = ["setuptools>=36.2", "wheel"]
build-backend = "setuptools.build_meta"
requires = ["uv_build>=0.7.19,<1.0.0"]
build-backend = "uv_build"

[project]
name = "nixtla"
dynamic = ["version"]
version = "0.7.3"
description = "Python SDK for Nixtla API (TimeGPT)"
authors = [
{name = "Nixtla", email = "business@nixtla.io"}
Expand Down Expand Up @@ -35,9 +35,6 @@ dependencies = [
"utilsforecast>=0.2.15",
]

[tool.setuptools.dynamic]
version = {attr = "nixtla.__version__"}

[project.optional-dependencies]
dev = [
"black",
Expand All @@ -55,7 +52,7 @@ dev = [
"pyreadr<0.5.3",
"python-dotenv",
"pyyaml",
"setuptools<79",
"ruff",
"statsforecast",
"tabulate",
"shap",
Expand Down Expand Up @@ -95,11 +92,8 @@ Homepage = "https://github.com/Nixtla/nixtla/"
Documentation = "https://nixtlaverse.nixtla.io/"
Repository = "https://github.com/Nixtla/nixtla/"

[tool.setuptools]
include-package-data = true

[tool.setuptools.packages.find]
exclude = ["action_files*"]
[tool.uv.build-backend]
module-root = "."

[tool.ruff.lint]
select = [
Expand Down
Loading