Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
76ea9cb
Updating requirements and fixing models.
zanpak Oct 9, 2024
b312354
Update the docker file
kavehtoyser Apr 16, 2026
c1b6cf2
Merge branch 'develop' into updating-requirements
kavehtoyser Apr 16, 2026
f3ffc1a
Upgrade all dependencies to latest versions with compatibility fixes
kavehtoyser Apr 16, 2026
c7cc8fd
Fix black formatting and pylint warnings from CI
kavehtoyser Apr 16, 2026
8b7675e
Disable new pylint checks introduced by unpinning pylint
kavehtoyser Apr 16, 2026
1565fbf
Fix pytest-manager CI job to use Python 3.12
kavehtoyser Apr 16, 2026
42fb1f8
Add httpx to dev requirements for FastAPI TestClient
kavehtoyser Apr 16, 2026
5dc8246
Fix test compatibility with upgraded dependencies
kavehtoyser Apr 16, 2026
392f288
Fix test compatibility with upgraded dependencies
kavehtoyser Apr 16, 2026
9c6a49d
Fix httpx and Jinja2 compatibility issues
kavehtoyser Apr 16, 2026
f754686
Fix TemplateResponse keyword args for Starlette compatibility
kavehtoyser Apr 16, 2026
b8585bd
Fix SDK tests and drop Python 3.9 from CI matrix
kavehtoyser Apr 16, 2026
0850fec
Replace pkg_resources with importlib.metadata
kavehtoyser Apr 16, 2026
e0fdf0b
Add pytest-timeout to prevent silent test hangs in CI
kavehtoyser Apr 24, 2026
7eeaf0a
Fix Pydantic v2 HttpUrl and Optional field issues in service API
kavehtoyser Apr 24, 2026
fe957ae
Fix test_logs_date_format to check output instead of stdout
kavehtoyser Apr 24, 2026
a5ef6c8
Replace setuptools.sandbox with subprocess in e2e wheel build
kavehtoyser Apr 24, 2026
4ab6c9b
Fix remaining e2e failures: scikit-learn rename and notebook kernel
kavehtoyser May 5, 2026
fc7e936
Bump pinned daeploy in pickle template from 0.4.6 to 1.3.1
kavehtoyser May 5, 2026
15ea802
Add diagnostics to pickle_service fixture
kavehtoyser May 5, 2026
51f4e65
Poll for pickle service container instead of fixed sleep
kavehtoyser May 5, 2026
55fb50f
Poll pickle service endpoint instead of container existence
kavehtoyser May 5, 2026
828963b
Bump pickle service test timeout to 900s
kavehtoyser May 5, 2026
ca97a8f
Add manager-log dump if pickle service polling times out
kavehtoyser May 5, 2026
709dded
Also dump pickle container logs on polling timeout
kavehtoyser May 5, 2026
3f38894
Regenerate pickle_e2e_testing.pkl with current scikit-learn
kavehtoyser May 5, 2026
5f793b3
Train pickle model on numpy arrays to avoid feature-name mismatch
kavehtoyser May 5, 2026
6379051
Print container logs and response body if pickle predict fails
kavehtoyser May 5, 2026
f1aad5f
Convert pickle service predictions to native Python types
kavehtoyser May 5, 2026
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
17 changes: 9 additions & 8 deletions .github/workflows/ci-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: "Set up Python 3"
uses: actions/setup-python@v2
with:
python-version: '3.8'
python-version: '3.12'
- name: "Install dependencies"
run: |
pip install --upgrade pip
Expand All @@ -38,7 +38,7 @@ jobs:
- name: "Set up Python 3"
uses: actions/setup-python@v2
with:
python-version: '3.8'
python-version: '3.12'
- name: "Install dependencies"
run: "pip install flake8"
- name: "Run flake8!"
Expand All @@ -53,10 +53,10 @@ jobs:
username: ${{ secrets.TEST_DOCKER_USERNAME }}
password: ${{ secrets.TEST_DOCKER_PASSWORD }}
- uses: actions/checkout@v2
- name: "Set up Python 3.8"
- name: "Set up Python 3.12"
uses: actions/setup-python@v2
with:
python-version: "3.8"
with:
python-version: "3.12"
- name: "Install dependencies"
run: |
pip install --upgrade pip
Expand All @@ -79,7 +79,7 @@ jobs:
needs: [black, pylint, flake8, pytest-manager]
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: ["3.10", "3.11", "3.12"]
steps:
- name: Login to Docker Hub
uses: docker/login-action@v1
Expand Down Expand Up @@ -118,12 +118,13 @@ jobs:
- name: "Set up Python 3"
uses: actions/setup-python@v2
with:
python-version: '3.8'
python-version: '3.12'
- name: "Install dependencies"
run: |
pip install --upgrade pip
pip install -r requirements_manager.txt
pip install -r requirements_manager.txt
pip install -r requirements_sdk.txt
pip install -r requirements_dev.txt
python -m ipykernel install --user --name python3
- name: "Running E2E tests with pytest"
run: "python -m pytest --verbose tests/e2e_test/"
17 changes: 12 additions & 5 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
[pylint]
disable =
disable =
R0801,
C0330,
C0326,
no-self-argument,
no-name-in-module,
too-few-public-methods,
too-many-arguments,
too-many-positional-arguments,
logging-fstring-interpolation,
fixme,
missing-module-docstring,
missing-function-docstring,
missing-class-docstring,
raise-missing-from,
unsubscriptable-object # TODO: Only required in python 3.9
unsubscriptable-object,
consider-using-with,
use-dict-literal,
missing-timeout,
unspecified-encoding,
useless-option-value,
invalid-name,
import-error

max-line-length = 88
max-line-length = 88
ignored-modules = IPython
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
## Stage 1: Build image
FROM python:3.8 AS build-image
FROM python:3.12 AS build-image

# Install S2i
RUN wget -c https://github.com/openshift/source-to-image/releases/download/v1.3.0/source-to-image-v1.3.0-eed2850f-linux-amd64.tar.gz \
Expand All @@ -20,7 +20,7 @@ COPY ./requirements_manager.txt .
RUN pip install -r requirements_manager.txt

## Stage 2: Production image
FROM python:3.8-slim AS production-image
FROM python:3.12-slim AS production-image

# Install Git
RUN apt-get update && apt-get install -y git
Expand Down
32 changes: 18 additions & 14 deletions daeploy/_service/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
from contextlib import contextmanager
import json

from sqlalchemy import create_engine, and_
from sqlalchemy import create_engine, and_, MetaData
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import sessionmaker, mapper, clear_mappers
from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy import Column, DateTime, Float, Text

from daeploy.utilities import get_db_table_limit
Expand All @@ -20,7 +20,7 @@
SERVICE_DB_PATH = Path("service_db.db")

ENGINE = create_engine(f"sqlite:///{str(SERVICE_DB_PATH)}")
Base = automap_base()
Base = declarative_base()
Session = sessionmaker(bind=ENGINE)

QUEUE = queue.Queue()
Expand Down Expand Up @@ -69,9 +69,6 @@ def create_new_ts_table(name: str, dtype: Type) -> Type:
# Create the actual table
MapperClass.__table__.create(ENGINE, checkfirst=True)

# Map everything
mapper(MapperClass, MapperClass.__table__)

LOGGER.info(f"Created new table for variable {name}")

return MapperClass
Expand Down Expand Up @@ -221,16 +218,17 @@ def initialize_db():
global QUEUE
QUEUE = queue.Queue()
global TABLES
Base.prepare(ENGINE, reflect=True) # Automap any existing tables
TABLES = dict(Base.classes) # Make sure we keep track of the auto-mapped tables
# Reflect any existing tables using automap
AutoBase = automap_base(metadata=MetaData())
AutoBase.prepare(autoload_with=ENGINE)
TABLES = dict(AutoBase.classes)
WRITER_THREAD.start()
LOGGER.info("DB started!")


def remove_db():
"""Remove db"""
global WRITER_THREAD
global QUEUE
global WRITER_THREAD, Base

# Stop and join writer thread if alive
if WRITER_THREAD.is_alive():
Expand All @@ -240,10 +238,16 @@ def remove_db():
# Reset it
WRITER_THREAD = threading.Thread(target=_writer, daemon=True)

# Reset tables tracking
TABLES.clear()

# Remove db
SERVICE_DB_PATH.unlink()
ENGINE.dispose()
try:
SERVICE_DB_PATH.unlink()
except FileNotFoundError:
pass

# Reset mappers and metadata object
clear_mappers()
Base.metadata.clear()
# Reset base so new tables get fresh mappers
Base = declarative_base()
LOGGER.info("DB has been shut down!")
7 changes: 3 additions & 4 deletions daeploy/_service/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from fastapi.encoders import jsonable_encoder
from fastapi.concurrency import run_in_threadpool
from fastapi.middleware.cors import CORSMiddleware
from pydantic import create_model, validate_arguments
from pydantic import create_model, validate_call

from daeploy._service.logger import setup_logging
from daeploy._service.db import clean_database, initialize_db, remove_db, write_to_ts
Expand All @@ -33,7 +33,6 @@
)
from daeploy.communication import notify, Severity


setup_logging()
logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -219,7 +218,7 @@ async def wrapper(_request: Request, *args, **kwargs):
_disable_http_logs(path)

# Wrap the original func in a pydantic validation wrapper and return that
return validate_arguments(deco_func)
return validate_call(deco_func)

# This ensures that we can use the decorator with or without arguments
if not (callable(func) or func is None):
Expand Down Expand Up @@ -370,7 +369,7 @@ def add_parameter(
if isinstance(value, Number):
value = float(value)

@validate_arguments()
@validate_call()
def update_parameter(value: value.__class__) -> Any:
logger.info(f"Parameter {parameter} changed to {value}")
self.parameters[parameter]["value"] = value
Expand Down
16 changes: 8 additions & 8 deletions daeploy/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import os
import json

import pkg_resources
from importlib.metadata import version as get_version, PackageNotFoundError
import pytest
import requests
import typer
Expand Down Expand Up @@ -73,9 +73,9 @@ def version_callback(value: bool):

# Get SDK Version
try:
sdk_version = pkg_resources.get_distribution("daeploy").version
sdk_version = get_version("daeploy")
typer.echo(f"SDK version: {sdk_version}")
except pkg_resources.DistributionNotFound:
except PackageNotFoundError:
pass

# Get Manager Version
Expand Down Expand Up @@ -654,13 +654,13 @@ def init(
raise typer.Exit(1)
# Find out which daeploy version that should be used by the service
try:
dist = pkg_resources.get_distribution("daeploy")
daeploy_version = get_version("daeploy")
daeploy_specifier = (
str(dist.as_requirement())
if dist.version != "0.0.0.dev0"
else dist.project_name
f"daeploy=={daeploy_version}"
if daeploy_version != "0.0.0.dev0"
else "daeploy"
) # Use full specificer unless in dev environment, then just go for the latest
except pkg_resources.DistributionNotFound:
except PackageNotFoundError:
typer.echo(
"`daeploy` package not found, assuming latest version "
"should be used for the generated project."
Expand Down
1 change: 0 additions & 1 deletion daeploy/cli/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from daeploy.cli import cliutils


app = typer.Typer(help="Collection of user management commands")
typer.Option(None, "-p", "--password", expose_value=False)

Expand Down
48 changes: 29 additions & 19 deletions daeploy/data_types.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
# pylint: disable=too-many-ancestors
# pylint: disable=too-many-ancestors, unused-argument
from typing import Any, List, Dict

import numpy as np
import pandas as pd
from pydantic import GetCoreSchemaHandler
from pydantic_core import core_schema


class ArrayInput(np.ndarray):
"""Pydantic compatible data type for numpy ndarray input."""

@classmethod
def __get_validators__(cls):
yield cls.validate
def __get_pydantic_core_schema__(
cls, source_type: Any, handler: GetCoreSchemaHandler
) -> core_schema.CoreSchema:
return core_schema.no_info_plain_validator_function(cls.validate)

@classmethod
def __modify_schema__(cls, field_schema):
field_schema.update(type="array", items={})
def __get_pydantic_json_schema__(cls, schema, handler):
return {"type": "array", "items": {}}

@classmethod
def validate(cls, value: List) -> np.ndarray:
Expand All @@ -26,12 +30,14 @@ class ArrayOutput(np.ndarray):
"""Pydantic compatible data type for numpy ndarray output."""

@classmethod
def __get_validators__(cls):
yield cls.validate
def __get_pydantic_core_schema__(
cls, source_type: Any, handler: GetCoreSchemaHandler
) -> core_schema.CoreSchema:
return core_schema.no_info_plain_validator_function(cls.validate)

@classmethod
def __modify_schema__(cls, field_schema):
field_schema.update(type="array", items={})
def __get_pydantic_json_schema__(cls, schema, handler):
return {"type": "array", "items": {}}

@classmethod
def validate(cls, value: np.ndarray) -> List:
Expand All @@ -43,31 +49,35 @@ class DataFrameInput(pd.DataFrame):
"""Pydantic compatible data type for pandas DataFrame input."""

@classmethod
def __get_validators__(cls):
yield cls.validate
def __get_pydantic_core_schema__(
cls, source_type: Any, handler: GetCoreSchemaHandler
) -> core_schema.CoreSchema:
return core_schema.no_info_plain_validator_function(cls.validate)

@classmethod
def __modify_schema__(cls, field_schema):
field_schema.update(type="object")
def __get_pydantic_json_schema__(cls, schema, handler):
return {"type": "object"}

@classmethod
def validate(cls, value: Dict[str, Any]) -> pd.DataFrame:
# Transform input to ndarray
# Transform input to DataFrame
return pd.DataFrame.from_dict(value)


class DataFrameOutput(pd.DataFrame):
"""Pydantic compatible data type for pandas DataFrame output."""

@classmethod
def __get_validators__(cls):
yield cls.validate
def __get_pydantic_core_schema__(
cls, source_type: Any, handler: GetCoreSchemaHandler
) -> core_schema.CoreSchema:
return core_schema.no_info_plain_validator_function(cls.validate)

@classmethod
def __modify_schema__(cls, field_schema):
field_schema.update(type="object")
def __get_pydantic_json_schema__(cls, schema, handler):
return {"type": "object"}

@classmethod
def validate(cls, value: pd.DataFrame) -> Dict[str, Any]:
# Transform input to ndarray
# Transform DataFrame to dict
return value.to_dict()
1 change: 0 additions & 1 deletion manager/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from manager.database import service_db
from manager.constants import get_manager_version, cors_enabled, cors_config


# Setup logger
logging_api.setup_logging()
LOGGER = logging.getLogger(__name__)
Expand Down
1 change: 1 addition & 0 deletions manager/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Constants and config
"""

import os
from pathlib import Path

Expand Down
Loading
Loading