Bump dependencies & add 3.12/13 support

This commit is contained in:
Andras Schmelczer 2026-10-27 16:17:58 +00:00
commit 6a8d669755
14 changed files with 130 additions and 85 deletions

View file

@ -7,6 +7,7 @@ on:
- 'docs/**'
- 'great_ai/**'
- 'mkdocs.yaml'
- 'pyproject.toml'
- '.forgejo/workflows/docs.yml'
workflow_dispatch:
@ -29,11 +30,11 @@ jobs:
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Set up Python
run: uv python install 3.10
run: uv python install 3.13
- name: Install dependencies
run: |
uv venv --python 3.10
uv venv --python 3.13
uv pip install '.[dev]'
- name: Build documentation

View file

@ -17,7 +17,7 @@ jobs:
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Set up Python
run: uv python install 3.10
run: uv python install 3.13
# Forgejo cannot use PyPI trusted publishing (OIDC), so authenticate with
# an API token, the same way the sibling repos publish their packages.

View file

@ -24,14 +24,14 @@ jobs:
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Set up Python
run: uv python install 3.10
run: uv python install 3.13
- name: Install dependencies
# --seed gives the venv its own pip, which scripts/check-python.sh shells
# out to for its linters; '.[dev]' provides the rest (mypy resolves the
# package's own imports against the installed tree).
run: |
uv venv --seed --python 3.10
uv venv --seed --python 3.13
uv pip install '.[dev]'
- name: Check code and formatting
@ -45,12 +45,7 @@ jobs:
strategy:
fail-fast: false
matrix:
# pyproject declares >= 3.7, but uv's standalone CPython and the current
# dependency floors (scikit-learn/numpy now require >= 3.9) no longer
# build on the older interpreters, so we test the current supported set.
# The windows leg of the old GitHub matrix is dropped: this Forgejo
# instance only has a Linux `docker` runner.
python-version: ['3.10', '3.11', '3.12', '3.13']
python-version: ['3.12', '3.13']
steps:
- uses: actions/checkout@v4

View file

@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1.4
FROM python:3.10.4-slim-bullseye
FROM python:3.13-slim-trixie
LABEL org.opencontainers.image.title="GreatAI package wrapper image"
LABEL org.opencontainers.image.vendor="ScoutinScience B.V."

View file

@ -59,6 +59,8 @@ However, [research indicates](https://great-ai.scoutinscience.com) that professi
## Find `great-ai` on [PyPI](https://pypi.org/project/great-ai/)
Requires Python 3.12 or later, including Python 3.13.
```sh
pip install great-ai
```
@ -78,8 +80,8 @@ Contributions are welcome.
```sh
python3 -m venv --copies .env
source .env/bin/activate
pip install --upgrade flit pip
flit install --symlink
pip install --upgrade pip
pip install --editable '.[dev]'
```
### Develop

View file

@ -5,18 +5,17 @@ Provided you already have [Python3](https://www.python.org/downloads/){ target=_
```sh
pip install great-ai
```
> Python 3.7 or later is required.
> Python 3.12 or later is required. Python 3.13 is supported.
This will work on all major operating systems.
## Google Colab
In order to use GreatAI in [Google Colab](https://colab.research.google.com){ target=_blank }, you need to downgrade `pyyaml` to a Colab compatible version. [See related StackOverflow question](https://stackoverflow.com/questions/69564817/typeerror-load-missing-1-required-positional-argument-loader-in-google-col){ target=_blank }.
To use GreatAI in [Google Colab](https://colab.research.google.com){ target=_blank }, select a runtime with Python 3.12 or later and install the package:
```sh
pip install great-ai pyyaml==5.4.1
pip install --upgrade great-ai
```
> This will make GreatAI work in Colab.
## Command-line tools
@ -50,4 +49,4 @@ You can also install the latest (usually unreleased) version from GitHub.
```sh
pip install --upgrade git+https://github.com/schmelczer/great-ai.git
```
> Python 3.7 or later is required.
> Python 3.12 or later is required.

View file

@ -50,7 +50,7 @@ After training and evaluating a model, it is exported using [great_ai.save_model
save_model(model, key='my-domain-predictor')
```
For more info, checkout [the configuration how-to page](/how-to-guides/configure-service).
For more info, check out [the configuration how-to page](/how-to-guides/configure-service).
### [Deployment notebook](deploy.ipynb)

View file

@ -16,8 +16,7 @@ def get_description(
style={"color": accent_color},
),
dcc.Markdown(
strip_lines(
f"""
strip_lines(f"""
> View the live data of your deployment here.
## Using the API
@ -27,8 +26,7 @@ def get_description(
## Details
{function_docs}
"""
),
"""),
className="description",
),
]

View file

@ -94,11 +94,9 @@ class ConfigFile(Mapping[str, str]):
try:
value = next(v for v in values if v)
except StopIteration:
raise ParseError(
f"""Cannot parse config file ({
raise ParseError(f"""Cannot parse config file ({
self._path.absolute()
}), error at key `{key}`"""
)
}), error at key `{key}`""")
already_exists = key in self._key_values
if already_exists and not value.startswith(

View file

@ -39,11 +39,9 @@ def manage_communication(
is_iteration_over = True
except Exception as e:
if ignore_exceptions:
logger.error(
f"""Exception {e} encountered in input, traceback:\n{
logger.error(f"""Exception {e} encountered in input, traceback:\n{
traceback.format_exc()
}"""
)
}""")
else:
raise

View file

@ -1,5 +1,5 @@
[build-system]
requires = ["flit_core >=3.2,<4"]
requires = ["flit_core >= 4.1.0, < 5"]
build-backend = "flit_core.buildapi"
[tool.flit.module]
@ -14,6 +14,8 @@ license = { file = "LICENSE" }
classifiers = [
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Libraries :: Python Modules",
"Typing :: Typed",
"Development Status :: 3 - Alpha",
@ -21,51 +23,51 @@ classifiers = [
"Natural Language :: English",
]
keywords = ["SE4ML", "MLOps", "AI engineering", "general", "robust", "end-to-end", "automated", "trustworthy", "ai", "deployment"]
requires-python = ">= 3.10"
requires-python = ">= 3.12"
dependencies = [
"scikit-learn >= 1.3, < 2",
"matplotlib >= 3.7, < 4",
"numpy >= 1.24, < 3",
"nbconvert >= 7.0, < 8",
"ipython >= 8.0, < 10",
"unidecode >= 1.3.0, < 2",
"syntok >= 1.4.0, < 2",
"langcodes[data] >= 3.3.0, < 4",
"scikit-learn >= 1.9.1, < 2",
"matplotlib >= 3.11.2, < 4",
"numpy >= 2.5.4, < 3",
"nbconvert >= 7.17.2, < 8",
"ipython >= 9.17.1, < 10",
"unidecode >= 1.4.0, < 2",
"syntok >= 1.4.4, < 2",
"langcodes[data] >= 3.5.1, < 4",
"langdetect >= 1.0.9, < 2",
"tinydb >= 4.7.0, < 5",
"boto3 >= 1.34, < 2",
"plotly >= 5.20, < 7",
"pandas >= 2.0, < 4",
"dash >= 2.16, < 5",
"fastapi >= 0.110, < 1",
"uvicorn[standard] >= 0.27, < 1",
"a2wsgi >= 1.9, < 2",
"watchdog >= 3.0, < 7",
"typeguard >= 4.0, < 5",
"pydantic >= 2.5, < 3",
"pymongo >= 4.6, < 5",
"dill >= 0.3.6, < 1",
"tqdm >= 4.64, < 5",
"httpx >= 0.24, < 1",
"tinydb >= 4.9.0, < 5",
"boto3 >= 1.43.119, < 2",
"plotly >= 7.1.0, < 8",
"pandas >= 3.1.0, < 4",
"dash >= 4.4.1, < 5",
"fastapi >= 0.162.0, < 1",
"uvicorn[standard] >= 0.54.0, < 1",
"a2wsgi >= 1.10.10, < 2",
"watchdog >= 6.0.0, < 7",
"typeguard >= 4.6.0, < 5",
"pydantic >= 2.14.1, < 3",
"pymongo >= 4.19.0, < 5",
"dill >= 0.4.1, < 1",
"tqdm >= 4.70.1, < 5",
"httpx >= 0.28.1, < 1",
]
[project.optional-dependencies]
dev = [
"flit >= 3.9, < 4",
"mkdocs >= 1.5, < 2",
"mkdocstrings[python] >= 0.24, < 1",
"mkdocs-material >= 9.5, < 10",
"mkdocs-jupyter >= 0.24, < 1",
"mkdocs-git-revision-date-localized-plugin >= 1.2, < 2",
"autoflake >= 2.0, < 3",
"isort >= 5.12, < 7",
"black[jupyter] >= 25.1, < 26",
"mypy >= 1.8, < 2",
"flake8 >= 7.0, < 8",
"tox >= 4.0, < 5",
"pytest >= 7.4, < 9",
"pytest-cov >= 4.1, < 8",
"pytest-asyncio >= 0.23, < 2",
"flit >= 4.1.0, < 5",
"mkdocs >= 1.6.1, < 2",
"mkdocstrings[python] >= 1.0.8, < 2",
"mkdocs-material >= 9.8.0, < 10",
"mkdocs-jupyter >= 0.27.0, < 1",
"mkdocs-git-revision-date-localized-plugin >= 1.6.2, < 2",
"autoflake >= 2.4.0, < 3",
"isort >= 9.0.2, < 10",
"black[jupyter] >= 26.10.1, < 27",
"mypy >= 2.4.0, < 3",
"flake8 >= 7.4.1, < 8",
"tox >= 4.67.2, < 5",
"pytest >= 9.1.1, < 10",
"pytest-cov >= 7.1.0, < 8",
"pytest-asyncio >= 1.4.1, < 2",
]
[tool.pytest.ini_options]

61
tests/test_http_api.py Normal file
View file

@ -0,0 +1,61 @@
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
from great_ai import GreatAI, Trace, configure, context
from great_ai.deploy.routes.dashboard.create_dash_app import update_charts
@pytest.mark.parametrize("is_async", [False, True])
def test_service(
is_async: bool, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
monkeypatch.setattr(context, "_context", None)
configure(version="1.2.3", disable_se4ml_banner=True)
def predict_sync(value: int) -> int:
return value + 2
async def predict_async(value: int) -> int:
return value + 2
service = GreatAI.create(predict_async if is_async else predict_sync)
with TestClient(service.app) as client:
response = client.post("/predict", json={"value": 3})
assert response.status_code == 200
trace = response.json()
assert trace["output"] == 5
assert trace["logged_values"]["arg:value:value"] == 3
trace_url = f"/traces/{trace['trace_id']}"
assert client.get(trace_url).json() == trace
assert client.post("/traces", json={}).json() == [trace]
assert client.post("/predict", json={"value": "invalid"}).status_code == 422
feedback_url = f"{trace_url}/feedback/"
assert client.put(feedback_url, json={"feedback": "correct"}).status_code == 202
assert client.get(feedback_url).json() == "correct"
assert client.delete(feedback_url).status_code == 204
assert client.get(feedback_url).json() is None
health = client.get("/health")
assert health.status_code == 200
assert health.json()["is_healthy"] is True
assert client.get("/version").json()["version"].split("+")[0] == "1.2.3"
assert client.get("/openapi.json").status_code == 200
assert client.get("/docs").status_code == 200
assert client.get("/dashboard/").status_code == 200
assert client.get("/dashboard/_dash-layout").status_code == 200
assert client.get("/assets/index.css").status_code == 200
histogram, coordinates, style = update_charts(
[Trace.model_validate(trace)], accent_color="#123456"
)
assert len(histogram.figure.data) == 1
assert len(coordinates.data) == 1
assert style == {}
assert client.delete(trace_url).status_code == 204
assert client.get(trace_url).status_code == 404

View file

@ -14,14 +14,11 @@ def test_simple() -> None:
assert c.first_key == "András"
assert c.second_key == "test 2"
assert c.third_key == "test= 2=="
assert (
c.fourth_key
== """
assert c.fourth_key == """
this#
is
multiline
"""
)
assert c.whitespace == "hardly matters"
with pytest.raises(KeyError):
c.this
@ -34,14 +31,11 @@ def test_simple_dict() -> None:
assert c["my_hashtag"] == "#great_ai"
assert c["second_key"] == "test 2"
assert c["third_key"] == "test= 2=="
assert (
c["fourth_key"]
== """
assert c["fourth_key"] == """
this#
is
multiline
"""
)
assert c["whitespace"] == "hardly matters"
with pytest.raises(KeyError):
c["#this"]
@ -55,14 +49,11 @@ def test_string_path() -> None:
assert c.second_key == "test 2"
assert c.third_key == "test= 2=="
assert (
c.fourth_key
== """
assert c.fourth_key == """
this#
is
multiline
"""
)
assert c.whitespace == "hardly matters"

View file

@ -1,5 +1,5 @@
[tox]
envlist = py37,py38,py39,py310
envlist = py312,py313
isolated_build = True
[testenv]