Bump dependencies & add 3.12/13 support
This commit is contained in:
parent
c55eba2077
commit
6a8d669755
14 changed files with 130 additions and 85 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
61
tests/test_http_api.py
Normal 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
|
||||
|
|
@ -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"
|
||||
|
||||
|
||||
|
|
|
|||
2
tox.ini
2
tox.ini
|
|
@ -1,5 +1,5 @@
|
|||
[tox]
|
||||
envlist = py37,py38,py39,py310
|
||||
envlist = py312,py313
|
||||
isolated_build = True
|
||||
|
||||
[testenv]
|
||||
|
|
|
|||
Loading…
Reference in a new issue